nom

Nom-like parser combinators for MoonBit.

parser
combinator
nom
moon add mizchi/nom@0.1.3
Download zip
Author
Version
0.1.3
License
Apache-2.0
Last updated
3 months ago
Downloads
26
README

#nom for MoonBit

Nom-like parser combinators for MoonBit. The core types mirror nom's IResult/Parser style while keeping MoonBit syntax and StringView/BytesView inputs.

  • IResult[I, O] = Result[(O, I), Err[ParseError[I]]]
  • Parser[I, O] = (I) -> IResult[I, O]

#Quick example (string)

///| Example: parse an integer with optional whitespace

fn parse_int_ws(input : StringView) -> @nom.IResult[StringView, Int] {
let parser = @nom.delimited(
@nom/str.space0,
@nom/str.int,
@nom/str.space0,
)
parser(input)
}

///|
test "parse int with ws" {
let input = " 123 "[:]
match parse_int_ws(input) {
Ok((value, rest)) => {
assert_eq(value, 123)
assert_true(rest.is_empty())
}
Err(_) => fail("parse failed")
}
}

#Packages

  • @nom - core combinators and error types
  • @nom/str - string parsers (StringView)
  • @nom/bytes - bytes parsers (BytesView)

Streaming is partially supported: parsers may return Err::Incomplete(Needed::Size(_)) when the input is too short. Use @nom/str.Stream or @nom/bytes.Stream to buffer chunks and retry the parser, or use @nom.complete(parser) to treat Incomplete as a normal error for non-streaming parsing.

///| Example: streaming string buffer
let parser = @nom/str.tag("abc"[:])
let stream0 = @nom/str.Stream::new()
let stream1 = stream0.feed("a"[:])
let (res1, stream1b) = stream1.parse(parser)
// res1 is Err::Incomplete(_)
let stream2 = stream1b.feed("bc"[:])
let (res2, stream3) = stream2.parse(parser)
// res2 is Ok(("abc", "")) and stream3 is now empty

#Benchmark vs Rust nom (local)

Numbers below are local microbenchmarks run on 2026-02-02. They are sensitive to machine and compiler versions, so treat them as rough guidance rather than absolute truth.

  • MoonBit: moon bench --target native
  • Rust: cargo bench --bench calculator and cargo bench --bench assignments_unicode

caseMoonBit (µs)Rust/nom (µs)ratio
calc short0.660.2592.55x
calc long3.821.7182.22x
calc complex short1.590.8151.95x
calc complex long4.072.1451.90x
assignments unicode short0.900.5911.52x
assignments unicode long3.712.6171.42x

Short, ASCII-heavy inputs tend to amplify constant overhead. As expressions get longer or more Unicode-heavy, the gap shrinks.

#
IResult

type IResult[I, O] = Result[(O, I), Err[ParseError[I]]]
Nom-style result type

#
Parser

type Parser[I, O] = (I) -> Result[(O, I), Err[ParseError[I]]]
Parser function type

#
InputLen

pub trait InputLen {
length(Self) -> Int
}
Input length trait for parsers that need to detect progress

#
Err

pub(all) enum Err[E] {
Error(E)
Failure(E)
Incomplete(Needed)
} derive(Eq, Show, ToJson)
Error wrapper (recoverable vs failure)

#
ErrorKind

pub(all) enum ErrorKind {
Tag
Take
TakeWhile
TakeWhile1
TakeUntil1
Char
Digit
Eof
MapRes
Alt
Many0
Custom(String)
} derive(Eq, Show, ToJson)
Error kinds similar to nom::error::ErrorKind

#
Needed

pub(all) enum Needed {
Unknown
Size(Int)
} derive(Eq, Show, ToJson)
Streaming-needed hint (like nom::Needed)

#
ParseError

pub struct ParseError[I] {
input : I
kind : ErrorKind
context : Array[String]
} derive(Eq, Show, ToJson)
Parse error with input snapshot and optional context stack

#
ParseError::new

fn[I] ParseError::new(input : I, kind : ErrorKind) -> ParseError[I]

#
ParseError::with_context

fn[I] ParseError::with_context(self : ParseError[I], label : String) -> ParseError[I]

#
all_consuming

fn[I : InputLen, O] all_consuming(parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(O, I), Err[ParseError[I]]])
Ensure the parser consumes all input

#
alt

fn[I, O] alt(parsers : Array[(I) -> Result[(O, I), Err[ParseError[I]]]]) -> ((I) -> Result[(O, I), Err[ParseError[I]]])
Try parsers in order, returning the first success

#
alt2

fn[I, O] alt2(first : (I) -> Result[(O, I), Err[ParseError[I]]], second : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(O, I), Err[ParseError[I]]])
Try two parsers in order, returning the first success

#
bytes_error_offset

fn bytes_error_offset(original : BytesView, err : ParseError[BytesView]) -> Int
Byte offset of a parsing error for BytesView inputs

#
complete

fn[I, O] complete(parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(O, I), Err[ParseError[I]]])
Convert streaming Incomplete into a normal error (for complete parsing)

#
context

fn[I, O] context(label : String, parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(O, I), Err[ParseError[I]]])
Attach context labels to errors

#
cut

fn[I, O] cut(parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(O, I), Err[ParseError[I]]])
Convert recoverable errors into failures (like nom::combinator::cut)

#
delimited

fn[I, O1, O2, O3] delimited(open : (I) -> Result[(O1, I), Err[ParseError[I]]], inner : (I) -> Result[(O2, I), Err[ParseError[I]]], close : (I) -> Result[(O3, I), Err[ParseError[I]]]) -> ((I) -> Result[(O2, I), Err[ParseError[I]]])
Parse with start/end delimiters

#
eof

fn[I : InputLen] eof(input : I) -> Result[(Unit, I), Err[ParseError[I]]]
Match end of input

#
fold_many0

fn[I : InputLen, O, R] fold_many0(parser : (I) -> Result[(O, I), Err[ParseError[I]]], init : () -> R, fold : (R, O) -> R) -> ((I) -> Result[(R, I), Err[ParseError[I]]])
Parse zero or more, folding into an accumulator (no allocations)

#
fold_many1

fn[I : InputLen, O, R] fold_many1(parser : (I) -> Result[(O, I), Err[ParseError[I]]], init : (O) -> R, fold : (R, O) -> R) -> ((I) -> Result[(R, I), Err[ParseError[I]]])
Parse one or more, folding into an accumulator (no allocations)

#
format_error_bytes

fn format_error_bytes(original : BytesView, err : Err[ParseError[BytesView]]) -> String
Format a bytes parsing error with offset and context

#
format_error_string

fn format_error_string(original : StringView, err : Err[ParseError[StringView]]) -> String
Format a string parsing error with location and context

#
many0

fn[I : InputLen, O] many0(parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(Array[O], I), Err[ParseError[I]]])
Parse zero or more, collecting results

#
many1

fn[I : InputLen, O] many1(parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(Array[O], I), Err[ParseError[I]]])
Parse one or more, collecting results

#
map

fn[I, O, O2] map(parser : (I) -> Result[(O, I), Err[ParseError[I]]], f : (O) -> O2) -> ((I) -> Result[(O2, I), Err[ParseError[I]]])
Map the output of a parser

#
map_err

fn[I, O] map_err(parser : (I) -> Result[(O, I), Err[ParseError[I]]], f : (ParseError[I]) -> ParseError[I]) -> ((I) -> Result[(O, I), Err[ParseError[I]]])
Map errors

#
map_res

fn[I, O, O2, E2] map_res(parser : (I) -> Result[(O, I), Err[ParseError[I]]], f : (O) -> Result[O2, E2], kind : ErrorKind) -> ((I) -> Result[(O2, I), Err[ParseError[I]]])
Map the output with a fallible function

#
opt

fn[I, O] opt(parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(O?, I), Err[ParseError[I]]])
Optional parser (never fails)

#
pair

fn[I, O1, O2] pair(first : (I) -> Result[(O1, I), Err[ParseError[I]]], second : (I) -> Result[(O2, I), Err[ParseError[I]]]) -> ((I) -> Result[((O1, O2), I), Err[ParseError[I]]])
Parse two parsers in sequence

#
preceded

fn[I, O1, O2] preceded(first : (I) -> Result[(O1, I), Err[ParseError[I]]], second : (I) -> Result[(O2, I), Err[ParseError[I]]]) -> ((I) -> Result[(O2, I), Err[ParseError[I]]])
Parse second after first, returning the second

#
separated_list0

fn[I : InputLen, O, S] separated_list0(sep : (I) -> Result[(S, I), Err[ParseError[I]]], parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(Array[O], I), Err[ParseError[I]]])
Parse a separated list (zero or more)

#
separated_list1

fn[I : InputLen, O, S] separated_list1(sep : (I) -> Result[(S, I), Err[ParseError[I]]], parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(Array[O], I), Err[ParseError[I]]])
Parse a separated list (one or more)

#
string_error_location

fn string_error_location(original : StringView, err : ParseError[StringView]) -> (Int, Int, Int)
Return (offset, line, column) for a StringView parsing error

#
string_error_offset

fn string_error_offset(original : StringView, err : ParseError[StringView]) -> Int
Code-unit offset of a parsing error for StringView inputs

#
terminated

fn[I, O1, O2] terminated(first : (I) -> Result[(O1, I), Err[ParseError[I]]], second : (I) -> Result[(O2, I), Err[ParseError[I]]]) -> ((I) -> Result[(O1, I), Err[ParseError[I]]])
Parse second after first, returning the first

#
to_error

fn[I] to_error(input : I, kind : ErrorKind) -> Err[ParseError[I]]

#
to_failure

fn[I] to_failure(input : I, kind : ErrorKind) -> Err[ParseError[I]]

#
to_incomplete

fn[E] to_incomplete(needed : Needed) -> Err[E]

#
value

fn[I, O, O2] value(v : O2, parser : (I) -> Result[(O, I), Err[ParseError[I]]]) -> ((I) -> Result[(O2, I), Err[ParseError[I]]])
Map the output, returning a constant value on success