parsec

Composable, token-generic parsers for MoonBit.

moonbit
parser
parser-combinator
parsing
moon add Nanaloveyuki/parsec@0.1.3
Download zip
Version
0.1.3
License
Apache-2.0
Last updated
7 days ago
Downloads
205
README

#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 = Parser::between(
Parser::token('(', expected="opening parenthesis"),
Parser::token('x', expected="x"),
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