parsec

    Composable, token-generic parsers for MoonBit.

    moonbit
    parser
    parser-combinator
    parsing
    Download zip
    Version
    0.1.4
    License
    Apache-2.0
    Last updated
    11 hours ago
    Downloads
    371

    #parsec

    parsec is a token-generic parser-combinator library for MoonBit. It makes small grammars composable without tying the library to a protocol, text encoding, or input domain.

    Parser[T, A] consumes an Array[T]: T is the input token and A is the parsed value. The concrete type keeps composition type-safe within MoonBit's current type system, without pretending to provide a language-level Monad abstraction.

    #Features

    • Parse any Array[T], including characters, bytes, integers, or domain tokens.
    • Compose parsers with map, flat_map, then, then_left, and then_right.
    • Match tokens with item, satisfy, and equality-based token.
    • Build alternatives with or_else, choice, attempt, and cut.
    • Repeat parsers with optional, option, many, many1, count, repeat_0_to_n, sep_by, and sep_by1 without zero-consumption infinite loops.
    • Compose homogeneous and heterogeneous sequences with sequence, lift2, and apply; use ParserRef for staged recursive grammars.
    • Build delimited, recursive, and lookahead grammars with between, delay, look_ahead, and not_followed_by.
    • Add user-facing expectations with label and nested grammar context with context.
    • Use optional feature packages for character-oriented factories and persistent pull-stream input without changing the root parser's array state.
    • Parse strict RFC 8259 documents with duplicate-key detection and resource limits through the optional json package.

    #Install

    moon add Nanaloveyuki/parsec

    Import the root package:

    import {
    "Nanaloveyuki/parsec",
    }

    Optional packages have separate imports and parser state:

    import {
    "Nanaloveyuki/parsec" @parsec,
    "Nanaloveyuki/parsec/char" @char,
    "Nanaloveyuki/parsec/lazy" @lazy,
    "Nanaloveyuki/parsec/lexer" @lexer,
    "Nanaloveyuki/parsec/json" @json,
    }

    • @char creates character parsers for the root package's Array[Char] input model.
    • @lazy provides an independent Stream[T] parser for persistent pull streams. Its parsers cannot be mixed with root parsers.
    • @lexer maps character offsets to source positions and spans for tokenizers and parse diagnostics. It does not own parser state.
    • @json is a strict JSON parser built from the root package and lexer. It is independent from JSON5, JSON-RPC, JSONPath, and JSONL libraries.

    #Use

    Build token parsers and finish a complete grammar with parse_all:

    let parser = @parsec.Parser::between(
    @parsec.Parser::token('(', expected="opening parenthesis"),
    @parsec.Parser::token('x', expected="x"),
    @parsec.Parser::token(')', expected="closing parenthesis"),
    )

    match parser.parse_all(['(', 'x', ')']) {
    Ok(value) => println(value)
    Err(error) => println("parse failed at \{error.offset()}")
    }

    or_else retries only when the first branch failed without consuming input. This makes a consumed prefix a commitment rather than silently discarding it.

    See the usage guide for grammar composition, error handling, recursive parsers, and feature-package boundaries.

    #Documentation

    #Nanaloveyuki/parsec

    parsec parses Array[T] inputs. A Parser[T, A] consumes zero or more tokens of type T and either produces A or a ParseError with an input offset.

    #Token parsers

    Use token for equality-based matching and finish a complete grammar with parse_all.

    ///|
    test "parse a delimited token" {
    let parser = @parsec.Parser::between(
    @parsec.Parser::token('(', expected="opening parenthesis"),
    @parsec.Parser::token('x', expected="x"),
    @parsec.Parser::token(')', expected="closing parenthesis"),
    )
    match parser.parse_all(['(', 'x', ')']) {
    Ok(value) => inspect(value, content="x")
    Err(_) => fail("expected a complete parse")
    }
    }

    #Composition

    map transforms a successful value. flat_map makes the next parser depend on that value. or_else retries its fallback only when the first parser failed without consuming input; this prevents silently discarding a committed prefix.

    many and many1 collect repetitions. A parser that succeeds without consuming input is rejected by many, preventing an infinite loop.

    position() observes the current token offset without consuming input. Feature packages use it when a parsed value must retain an exact source offset while the root execution state remains private.

    count(n) parses exactly n values, while repeat_0_to_n(n) parses at most n. sep_by1 requires a non-empty separated list; option(default) keeps a plain result type when an unconsumed failure should select a default value.

    sequence, lift2, and apply provide applicative composition. For recursive grammars assembled in stages, create a ParserRef[T, A], build parsers with parser(), then call set(...) before parsing. Running an unbound reference returns UnboundReference.

    ParseError

    pub(all) enum ParseError {
    UnexpectedEnd(offset~ : Int)
    Expected(label~ : String, offset~ : Int)
    ExpectedAny(labels~ : Array[String], offset~ : Int)
    EmptyMatchInMany(offset~ : Int)
    EmptyChoice(offset~ : Int)
    UnboundReference(offset~ : Int)
    NotFollowedBy(label~ : String, offset~ : Int)
    Context(label~ : String, cause~ : ParseError)
    }

    ParseError::offset

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

    Parser

    pub struct Parser[T, A] {
    // private fields
    }

    Parser::apply

    fn[T, A, B] Parser::apply(self : Parser[T, (A) -> B], argument : Parser[T, A]) -> Parser[T, B]

    Applies a parsed function to the value parsed by argument.

    Parser::attempt

    fn[T, A] Parser::attempt(self : Parser[T, A]) -> Parser[T, A]

    Parser::between

    fn[T, Open, A, Close] Parser::between(open : Parser[T, Open], parser : Parser[T, A], close : Parser[T, Close]) -> Parser[T, A]

    Parser::choice

    fn[T, A] Parser::choice(parsers : Array[Parser[T, A]]) -> Parser[T, A]

    Parser::context

    fn[T, A] Parser::context(self : Parser[T, A], label : String) -> Parser[T, A]

    Parser::count

    fn[T, A] Parser::count(self : Parser[T, A], count : Int) -> Parser[T, Array[A]]

    Parses exactly count occurrences. Non-positive counts succeed with no values and consume no input.

    Parser::cut

    fn[T] Parser::cut() -> Parser[T, Unit]

    Parser::delay

    fn[T, A] Parser::delay(factory : () -> Parser[T, A]) -> Parser[T, A]

    Parser::eof

    fn[T] Parser::eof() -> Parser[T, Unit]

    Parser::fail

    fn[T, A] Parser::fail(error : ParseError) -> Parser[T, A]

    Parser::flat_map

    fn[T, A, B] Parser::flat_map(self : Parser[T, A], next : (A) -> Parser[T, B]) -> Parser[T, B]

    Parser::ignore

    fn[T, A] Parser::ignore(self : Parser[T, A]) -> Parser[T, Unit]

    Parser::item

    fn[T] Parser::item() -> Parser[T, T]

    Parser::label

    fn[T, A] Parser::label(self : Parser[T, A], label : String) -> Parser[T, A]

    Parser::lift2

    fn[T, A, B, C] Parser::lift2(left : Parser[T, A], right : Parser[T, B], combine : (A, B) -> C) -> Parser[T, C]

    Combines the successful values of two parsers.

    Parser::look_ahead

    fn[T, A] Parser::look_ahead(self : Parser[T, A]) -> Parser[T, A]

    Parser::many

    fn[T, A] Parser::many(self : Parser[T, A]) -> Parser[T, Array[A]]

    Parser::many1

    fn[T, A] Parser::many1(self : Parser[T, A]) -> Parser[T, Array[A]]

    Parser::map

    fn[T, A, B] Parser::map(self : Parser[T, A], transform : (A) -> B) -> Parser[T, B]

    Parser::not_followed_by

    fn[T, A] Parser::not_followed_by(self : Parser[T, A], expected~ : String) -> Parser[T, Unit]

    Parser::option

    fn[T, A] Parser::option(self : Parser[T, A], default : A) -> Parser[T, A]

    Uses default when this parser fails before consuming input.

    Parser::optional

    fn[T, A] Parser::optional(self : Parser[T, A]) -> Parser[T, A?]

    Parser::or_else

    fn[T, A] Parser::or_else(self : Parser[T, A], fallback : Parser[T, A]) -> Parser[T, A]

    Parser::parse

    fn[T, A] Parser::parse(self : Parser[T, A], input : Array[T]) -> Result[A, ParseError]

    Parser::parse_all

    fn[T, A] Parser::parse_all(self : Parser[T, A], input : Array[T]) -> Result[A, ParseError]

    Parser::position

    fn[T] Parser::position() -> Parser[T, Int]

    Succeeds without consuming input and returns the current token offset.

    Parser::pure

    fn[T, A] Parser::pure(value : A) -> Parser[T, A]

    Parser::repeat_0_to_n

    fn[T, A] Parser::repeat_0_to_n(self : Parser[T, A], max_count : Int) -> Parser[T, Array[A]]

    Parses at most max_count occurrences. It stops at an unconsumed failure. Non-positive bounds succeed with no values and consume no input.

    Parser::replace

    fn[T, A, B] Parser::replace(self : Parser[T, A], value : B) -> Parser[T, B]

    Parser::run

    fn[T, A] Parser::run(self : Parser[T, A], state : State[T]) -> Result[(A, State[T]), ParseError]

    Parser::satisfy

    fn[T] Parser::satisfy(label : String, predicate : (T) -> Bool) -> Parser[T, T]

    Parser::sep_by

    fn[T, A, S] Parser::sep_by(self : Parser[T, A], separator : Parser[T, S]) -> Parser[T, Array[A]]

    Parser::sep_by1

    fn[T, A, S] Parser::sep_by1(self : Parser[T, A], separator : Parser[T, S]) -> Parser[T, Array[A]]

    Parses one or more values separated by separator.

    Parser::sequence

    fn[T, A] Parser::sequence(parsers : Array[Parser[T, A]]) -> Parser[T, Array[A]]

    Runs every parser in order and collects their values.

    Parser::then

    fn[T, A, B] Parser::then(self : Parser[T, A], next : Parser[T, B]) -> Parser[T, (A, B)]

    Parser::then_left

    fn[T, A, B] Parser::then_left(self : Parser[T, A], next : Parser[T, B]) -> Parser[T, A]

    Parser::then_right

    fn[T, A, B] Parser::then_right(self : Parser[T, A], next : Parser[T, B]) -> Parser[T, B]

    Parser::token

    fn[T : Eq] Parser::token(token : T, expected~ : String) -> Parser[T, T]

    ParserRef

    pub struct ParserRef[T, A] {
    // private fields
    }

    A mutable parser slot for recursive grammars assembled in multiple steps.

    ParserRef::new

    fn[T, A] ParserRef::new() -> ParserRef[T, A]

    Creates an unbound recursive parser slot.

    ParserRef::parser

    fn[T, A] ParserRef::parser(self : ParserRef[T, A]) -> Parser[T, A]

    Returns a parser that delegates to the slot's current binding at run time.

    ParserRef::set

    fn[T, A] ParserRef::set(self : ParserRef[T, A], parser : Parser[T, A]) -> Unit

    Binds subsequent executions of this slot to parser.

    Reply

    type Reply[T, A]

    State

    pub struct State[T] {
    // private fields
    }

    State::is_at_end

    fn[T] State::is_at_end(self : State[T]) -> Bool

    State::new

    fn[T] State::new(input : Array[T]) -> State[T]

    State::offset

    fn[T] State::offset(self : State[T]) -> Int

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io