talcparsec

    parser
    parsec
    combinator
    pratt
    Download zip
    Author
    Version
    0.3.1
    License
    AGPL-3.0
    Last updated
    2 hours ago
    Downloads
    7K

    #

    A fast, trait-driven parser combinator library for MoonBit.

    talcparsec builds hand-written recursive-descent parsers out of small composable combinators. It targets the same niche as Parsec and Attoparsec: embedded languages, DSLs, interpreters, and source-code tooling where you want the structure of a grammar without a parser generator.

    #Highlights

    • Commit-aware backtracking — consuming input commits a parse attempt; once a branch commits, alternatives are no longer tried. Backtracking is opt-in via attempt, so failures never re-scan input silently.
    • Good errors by default — failures carry the furthest position reached, and alternatives at the same position merge their expected labels. Every error is structured data (offset, line, column, expected labels, committed flag), so diagnostics are easy to render or recover from.
    • Open error modelParserRaw[I, T, E] is generic over input, result, and error type. A custom error type only needs the Commit + CanMerge + Positioned + ParseFailure traits; the full combinator stack and the primitive parsers work with any E.
    • Fast on hot paths — tokenizer loops avoid allocation, line and column positions are computed lazily from a line-start table, and all offsets are UTF-16 code units matching MoonBit string indexing.

    #Add as a dependency

    moon add kokic/talcparsec

    #Design

    #Trait-based error hierarchy

    The error model is four layered traits, all implemented by the default ParseError struct:

    • Commit — whether a failure forbids backtracking;
    • CanMerge — how two failures at the same position combine;
    • Positioned — the source position of a failure;
    • ParseFailure — construction from an input cursor, with signal (expected-label failures) and message (free-form failures).

    Because the combinators are written against the traits rather than the concrete error type, an application can substitute its own error type — with structured payloads of its choosing — and use every built-in combinator and primitive unchanged.

    #Input as a trait

    Parsers are parameterized over the Cursor trait (cursor, position, is_at_eof, same_cursor), so the same combinators work on the built-in string Input and on custom token streams.

    #Explicit control flow

    There is no hidden backtracking. A parser either commits (input was consumed) or fails uncommitted; attempt clears the commit so the next alternative is tried. label improves error messages without backtracking, and not_followed_by provides negative lookahead. Repetition combinators reject parsers that accept empty input instead of looping forever.

    CParser

    type CParser = ParserRaw[Input, Char, ParseError]

    Generic parser type parameterised by input I, result T, and error E. The convenience alias Parser[I, T] fixes E = ParseError for common use.

    Parser

    type Parser[I, T] = ParserRaw[I, T, ParseError]

    Convenience alias using the default ParseError.

    SParser

    type SParser = ParserRaw[Input, String, ParseError]

    Generic parser type parameterised by input I, result T, and error E. The convenience alias Parser[I, T] fixes E = ParseError for common use.

    StringParser

    type StringParser[T] = ParserRaw[Input, T, ParseError]

    Convenience alias for Parser[Input, T], used for string-input parsers.

    CanMerge

    pub(open) trait CanMerge {
    fn merge(Self, other : Self) -> Self
    }

    Error types whose alternatives can be merged. Furthest position wins; expected labels are combined.

    Commit

    pub(open) trait Commit {
    fn is_committed(Self) -> Bool
    fn mark_committed(Self) -> Self
    fn clear_commit(Self) -> Self
    fn[I, T] reject_if_committed(Self, value : T, rest : I) -> Result[(T, I), Self]
    }

    Types that track parse commitment — when a parser consumes input and then fails, the error is "committed" to prevent backtracking.

    Cursor

    pub(open) trait Cursor {
    fn cursor(Self) -> Int
    fn position(Self) -> Position
    fn is_at_eof(Self) -> Bool
    fn same_cursor(Self, other : Self) -> Bool = _
    }

    Operations shared by parser input cursors. cursor must increase whenever an input element is consumed.

    ParseFailure

    pub(open) trait ParseFailure : Commit + CanMerge + Positioned {
    fn[I : Cursor] signal(input : I, expected : String) -> Self
    fn[I : Cursor] message(input : I, msg : String) -> Self
    }

    Full parsing failure contract. Combines commit tracking, error merging, source positioning, and construction from a cursor.

    Positioned

    pub(open) trait Positioned {
    fn error_offset(Self) -> Int
    fn error_line(Self) -> Int
    fn error_column(Self) -> Int
    }

    Error types that carry a source position (offset, line, column).

    Input

    pub(all) struct Input {
    source : String
    offset : Int
    line_starts : Array[Int]
    } derive(Eq,
    Debug
    )

    Immutable character input cursor. Offsets are UTF-16 code unit offsets, matching MoonBit string indexing. Line/column positions are computed lazily from line_starts on demand.
    impl Cursor for Input

    Input::advance

    fn Input::advance(self : Input, ch : Char) -> Input

    Input::advance_string

    fn Input::advance_string(self : Input, text : String) -> Input

    Input::cursor

    fn Input::cursor(self : Input) -> Int

    Input::equal

    fn Input::equal(Input, Input) -> Bool

    Input::is_at_eof

    fn Input::is_at_eof(self : Input) -> Bool

    Input::is_eof

    fn Input::is_eof(self : Input) -> Bool

    Input::new

    fn Input::new(source : String) -> Input

    Input::next

    fn Input::next(self : Input) -> (Char, Input)?

    Input::not_equal

    fn Input::not_equal(x : Input, y : Input) -> Bool

    Input::peek

    fn Input::peek(self : Input) -> Char?

    Input::position

    fn Input::position(self : Input) -> Position

    Input::remaining

    fn Input::remaining(self : Input) -> StringView

    Input::same_cursor

    fn Input::same_cursor(self : Input, other : Input) -> Bool

    Input::to_repr

    ParseError

    pub(all) struct ParseError {
    offset : Int
    line : Int
    column : Int
    expected : String
    alternatives : Array[String]?
    message : String
    committed : Bool
    } derive(Eq,
    Debug
    )

    Parse failure metadata. committed prevents alternatives from backtracking across input that has already been consumed.

    ParseError::clear_commit

    fn ParseError::clear_commit(self : ParseError) -> ParseError

    ParseError::column

    fn ParseError::column(self : ParseError) -> Int

    ParseError::equal

    fn ParseError::equal(ParseError, ParseError) -> Bool

    ParseError::error_column

    fn ParseError::error_column(self : ParseError) -> Int

    ParseError::error_line

    fn ParseError::error_line(self : ParseError) -> Int

    ParseError::error_offset

    fn ParseError::error_offset(self : ParseError) -> Int

    ParseError::expected

    fn ParseError::expected(self : ParseError) -> Array[String]

    ParseError::is_committed

    fn ParseError::is_committed(self : ParseError) -> Bool

    ParseError::line

    fn ParseError::line(self : ParseError) -> Int

    ParseError::mark_committed

    fn ParseError::mark_committed(self : ParseError) -> ParseError

    ParseError::merge

    fn ParseError::merge(self : ParseError, other : ParseError) -> ParseError

    ParseError::message

    fn[I : Cursor] ParseError::message(input : I, msg : String) -> ParseError

    ParseError::message_text

    fn ParseError::message_text(self : ParseError) -> String

    ParseError::new

    fn[I : Cursor] ParseError::new(input : I, expected : String) -> ParseError

    ParseError::not_equal

    fn ParseError::not_equal(x : ParseError, y : ParseError) -> Bool

    ParseError::offset

    fn ParseError::offset(self : ParseError) -> Int

    ParseError::reject_if_committed

    fn[I, T] ParseError::reject_if_committed(self : ParseError, value : T, rest : I) -> Result[(T, I), ParseError]

    ParseError::signal

    fn[I : Cursor] ParseError::signal(input : I, expected : String) -> ParseError

    ParseError::to_string

    fn ParseError::to_string(self : ParseError) -> String

    ParseError::with_commit

    fn ParseError::with_commit(self : ParseError) -> ParseError

    ParseError::without_commit

    fn ParseError::without_commit(self : ParseError) -> ParseError

    ParserRaw

    pub(all) struct ParserRaw[I, T, E] {
    parse_fn : (I) -> Result[(T, I), E]
    }

    Generic parser type parameterised by input I, result T, and error E. The convenience alias Parser[I, T] fixes E = ParseError for common use.

    ParserRaw::after

    fn[I : Cursor, T, L, E : Commit] ParserRaw::after(self : ParserRaw[I, T, E], left : ParserRaw[I, L, E]) -> ParserRaw[I, T, E]

    Runs left then self, returning self's result.

    Parameters:

    • self : The parser whose result is returned.
    • left : The parser whose result is discarded.

    Equivalent to left *> self.

    ParserRaw::attempt

    fn[I, T, E : Commit] ParserRaw::attempt(self : ParserRaw[I, T, E]) -> ParserRaw[I, T, E]

    Strips the committed flag from any error.

    Parameters:

    • self : The parser to wrap.

    Enables backtracking across input consumed by self. Use sparingly — prefer grammars where alternatives are distinguishable without backtracking.

    ParserRaw::before

    fn[I : Cursor, T, R, E : Commit] ParserRaw::before(self : ParserRaw[I, T, E], right : ParserRaw[I, R, E]) -> ParserRaw[I, T, E]

    Runs self then right, returning self's result.

    Parameters:

    • self : The parser whose result is returned.
    • right : The parser whose result is discarded.

    Equivalent to self <* right.

    ParserRaw::between

    fn[I : Cursor, T, L, R, E : Commit] ParserRaw::between(self : ParserRaw[I, T, E], left : ParserRaw[I, L, E], right : ParserRaw[I, R, E]) -> ParserRaw[I, T, E]

    Runs left, then self, then right, returning self's result.

    Parameters:

    • self : The middle parser whose result is returned.
    • left : The opening parser; its result is discarded.
    • right : The closing parser; its result is discarded.

    Equivalent to left.then(self).skip(right).

    ParserRaw::bind

    fn[I : Cursor, T, U, E : Commit] ParserRaw::bind(self : ParserRaw[I, T, E], next : (T) -> ParserRaw[I, U, E]) -> ParserRaw[I, U, E]

    Monadic bind. Runs self, passes the result to next.

    Parameters:

    • self : The first parser to run.
    • next : A function that takes the result of self and returns the next parser to run.

    If self consumed input, any error from next is marked committed, preventing backtracking across consumed input.

    Returns a parser that runs self, then feeds its result to next.

    ParserRaw::label

    fn[I : Cursor, T, E : ParseFailure + Commit + CanMerge + Positioned] ParserRaw::label(self : ParserRaw[I, T, E], expected : String) -> ParserRaw[I, T, E]

    Replaces the expected label in the error if self fails without having advanced the cursor.

    Parameters:

    • self : The parser to label.
    • expected : The label to report on failure.

    If input was consumed before failure, the original error is preserved (furthest position wins). A replaced label keeps the original error's committed flag.

    ParserRaw::lexeme

    fn[T, E] ParserRaw::lexeme(self : ParserRaw[Input, T, E]) -> ParserRaw[Input, T, E]

    Wraps self as a lexeme by skipping trailing whitespace.

    Parameters:

    • self : The parser to wrap.

    Returns a parser that runs self then skips whitespace.

    ParserRaw::many

    fn[I : Cursor, T, E : Commit + ParseFailure + CanMerge + Positioned] ParserRaw::many(self : ParserRaw[I, T, E]) -> ParserRaw[I, Array[T], E]

    Zero or more repetitions of self.

    Stops on non-committed failure. Rejects parsers that accept empty input (infinite loop guard).

    ParserRaw::many_until

    fn[I : Cursor, T, U, E : Commit + ParseFailure + CanMerge + Positioned] ParserRaw::many_until(self : ParserRaw[I, T, E], terminator : ParserRaw[I, U, E]) -> ParserRaw[I, Array[T], E]

    Keeps running self until terminator succeeds.

    ParserRaw::map

    fn[I, T, U, E] ParserRaw::map(self : ParserRaw[I, T, E], f : (T) -> U) -> ParserRaw[I, U, E]

    Transforms the result of a successful parse using f.

    Parameters:

    • self : The parser whose result to transform.
    • f : The transformation function.

    Returns a parser that parses the same input as self and applies f to the result on success.

    ParserRaw::new

    fn[I, T, E] ParserRaw::new(parse_fn : (I) -> Result[(T, I), E]) -> ParserRaw[I, T, E]

    ParserRaw::not_followed_by

    fn[I : Cursor, T, E : ParseFailure + Commit + CanMerge + Positioned] ParserRaw::not_followed_by(self : ParserRaw[I, T, E], label : String) -> ParserRaw[I, Unit, E]

    Negative lookahead. Succeeds with () (consuming no input) when self fails.

    ParserRaw::optional

    fn[I, T, E : Commit] ParserRaw::optional(self : ParserRaw[I, T, E]) -> ParserRaw[I, T?, E]

    Runs self and returns Some(result) on success.

    Parameters:

    • self : The parser to try.

    Returns None on non-committed failure. Committed failures propagate immediately.

    ParserRaw::or

    fn[I, T, E : Commit + CanMerge] ParserRaw::or(self : ParserRaw[I, T, E], other : ParserRaw[I, T, E]) -> ParserRaw[I, T, E]

    Choice with commit-aware backtracking.

    Parameters:

    • self : The first alternative.
    • other : The second alternative, tried if self fails without committing.

    If self fails with committed error, other is NOT tried. Errors are merged by furthest position.

    Returns the result of the first successful alternative.

    ParserRaw::parse

    fn[I : Cursor, T, E : ParseFailure + Commit + CanMerge + Positioned] ParserRaw::parse(self : ParserRaw[I, T, E], input : I) -> Result[T, E]

    ParserRaw::parse_partial

    fn[I, T, E] ParserRaw::parse_partial(self : ParserRaw[I, T, E], input : I) -> Result[(T, I), E]

    ParserRaw::run

    fn[I, T, E] ParserRaw::run(self : ParserRaw[I, T, E], input : I) -> Result[(T, I), E]

    ParserRaw::sep_by

    fn[I : Cursor, T, S, E : Commit + CanMerge + ParseFailure + Positioned] ParserRaw::sep_by(self : ParserRaw[I, T, E], sep : ParserRaw[I, S, E]) -> ParserRaw[I, Array[T], E]

    Zero or more repetitions of self separated by sep.

    ParserRaw::sep_by1

    fn[I : Cursor, T, S, E : Commit + ParseFailure + CanMerge + Positioned] ParserRaw::sep_by1(self : ParserRaw[I, T, E], separator : ParserRaw[I, S, E]) -> ParserRaw[I, Array[T], E]

    One or more repetitions of self separated by separator.

    ParserRaw::skip

    fn[I : Cursor, T, U, E : Commit] ParserRaw::skip(self : ParserRaw[I, T, E], next : ParserRaw[I, U, E]) -> ParserRaw[I, T, E]

    Runs self then next, returning self's result.

    Parameters:

    • self : The first parser; its result is returned.
    • next : The second parser; its result is discarded.

    Equivalent to self <* next.

    ParserRaw::some

    fn[I : Cursor, T, E : Commit + ParseFailure + CanMerge + Positioned] ParserRaw::some(self : ParserRaw[I, T, E]) -> ParserRaw[I, Array[T], E]

    One or more repetitions of self.

    ParserRaw::string_map

    fn[U, E : Commit] ParserRaw::string_map(self : ParserRaw[Input, Char, E], f : (String) -> U) -> ParserRaw[Input, U, E]

    Maps a char parser's result to a String before applying f.

    ParserRaw::then

    fn[I : Cursor, T, U, E : Commit] ParserRaw::then(self : ParserRaw[I, T, E], next : ParserRaw[I, U, E]) -> ParserRaw[I, U, E]

    Runs self then next, discarding self's result.

    Parameters:

    • self : The first parser; its result is discarded.
    • next : The second parser; its result is returned.

    Equivalent to self >> next.

    Position

    pub(all) struct Position {
    offset : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    A source position reported by parser diagnostics.

    Position::equal

    fn Position::equal(Position, Position) -> Bool

    Position::not_equal

    fn Position::not_equal(x : Position, y : Position) -> Bool

    Position::to_repr

    ascii_digit

    fn[E : ParseFailure + Commit + CanMerge + Positioned] ascii_digit() -> ParserRaw[Input, Char, E]

    ascii_letter

    fn[E : ParseFailure + Commit + CanMerge + Positioned] ascii_letter() -> ParserRaw[Input, Char, E]

    between

    fn[I : Cursor, T, L, R, E : Commit] between(left : ParserRaw[I, L, E], parser : ParserRaw[I, T, E], right : ParserRaw[I, R, E]) -> ParserRaw[I, T, E]

    Standalone version of Parser::between.

    char

    fn[E : ParseFailure + Commit + CanMerge + Positioned] char(expected : Char) -> ParserRaw[Input, Char, E]

    choice

    fn[I : Cursor, T, E : Commit + CanMerge + ParseFailure + Positioned] choice(parsers : Array[ParserRaw[I, T, E]]) -> ParserRaw[I, T, E]

    Tries each parser in the array in order on the same input.

    Iterates through parsers, accumulating errors via CanMerge::merge. On committed failure, propagation is immediate — no further alternatives are tried. If no parser succeeds, the merged error (or a fallback message for an empty array) is returned.

    delay

    fn[I, T, E] delay(thunk : () -> ParserRaw[I, T, E]) -> ParserRaw[I, T, E]

    eof

    fn[I : Cursor, E : ParseFailure + Commit + CanMerge + Positioned] eof() -> ParserRaw[I, Unit, E]

    fail

    fn[I : Cursor, T, E : ParseFailure + Commit + CanMerge + Positioned] fail(expected : String) -> ParserRaw[I, T, E]

    lexeme

    fn[T, E] lexeme(parser : ParserRaw[Input, T, E]) -> ParserRaw[Input, T, E]

    Standalone version of Parser::lexeme.

    line_content

    let line_content : ParserRaw[Input, String, ParseError]

    many_chars

    fn[E : Commit + ParseFailure + CanMerge + Positioned] many_chars(parser : ParserRaw[Input, Char, E]) -> ParserRaw[Input, String, E]

    Zero or more repetitions of a char parser, collecting results into a String.

    Unlike take_while, succeeds on zero matches (returns empty string).

    many_chars1

    fn[E : Commit + ParseFailure + CanMerge + Positioned] many_chars1(parser : ParserRaw[Input, Char, E]) -> ParserRaw[Input, String, E]

    One or more repetitions of a char parser, collecting results into a String.

    Fails on zero matches.

    newline

    let newline : ParserRaw[Input, Char, ParseError]

    none_of

    fn[E : ParseFailure + Commit + CanMerge + Positioned] none_of(chars : String) -> ParserRaw[Input, Char, E]

    not_newline

    let not_newline : ParserRaw[Input, Char, ParseError]

    one_of

    fn[E : ParseFailure + Commit + CanMerge + Positioned] one_of(chars : String) -> ParserRaw[Input, Char, E]

    pure

    fn[I, T, E] pure(value : T) -> ParserRaw[I, T, E]

    rest_of_line

    let rest_of_line : ParserRaw[Input, String, ParseError]

    Consumes all non-newline characters, then consumes the trailing newline.

    Returns the matched line content including the newline terminator.

    satisfy

    fn[E : ParseFailure + Commit + CanMerge + Positioned] satisfy(name : String, predicate : (Char) -> Bool) -> ParserRaw[Input, Char, E]

    satisfy_input

    fn[I : Cursor, T, E : ParseFailure + Commit + CanMerge + Positioned] satisfy_input(next : (I) -> (T, I)?, name : String, predicate : (T) -> Bool) -> ParserRaw[I, T, E]

    skip_spaces

    fn[E] skip_spaces() -> ParserRaw[Input, Unit, E]

    space

    let space : ParserRaw[Input, Char, ParseError]

    spaces

    fn[E] spaces() -> ParserRaw[Input, Array[Char], E]

    string

    fn[E : ParseFailure + Commit + CanMerge + Positioned] string(expected : String) -> ParserRaw[Input, String, E]

    symbol

    fn[E : ParseFailure + Commit + CanMerge + Positioned] symbol(text : String) -> ParserRaw[Input, String, E]

    Matches an exact string, then skips trailing whitespace.

    take_while

    fn[E : ParseFailure + Commit + CanMerge + Positioned] take_while(name : String, predicate : (Char) -> Bool) -> ParserRaw[Input, String, E]

    whitespace

    fn[E : ParseFailure + Commit + CanMerge + Positioned] whitespace() -> ParserRaw[Input, Char, E]

    with_line

    fn with_line(String) -> ParserRaw[Input, String, ParseError]

    Maps content c to a parser that consumes a newline and returns c + a newline character.