A parser-monad combinator library for Rust. The core crate of oni-comb-rs.
- Parsec-style recursive descent — LL(1) by default, LL(*) with
attempt - Full type class hierarchy — Functor (
map) / Applicative (zip) / Alternative (or) / Monad (flat_map) - Zero-cost combinator composition — Applicative combinators are concrete types on the stack, zero heap allocation
- Backtrack / Cut error control —
orrecovers fromBacktrack;Cutpropagates.attempt/cutto control - Structured errors —
ParseErrorwith position, expected tokens, and.context()labels - Generic InputStream trait —
StrInputStream<'a>for&str,ByteInputStream<'a>for&[u8] no_stdsupport —#![no_std]withalloc
- Full JSON benchmark rerun (March 21, 2026, predictive-choice pass) —
300.5 µs,339.6 MiB/son the 107KB sample; still behindwinnow, roughly in the same range asnom, and now clearly ahead ofchumskyandpom - Generic token parsers remain competitive in the latest
comparisonrerun — identifier"foo_bar_123"is20.0 ns, while integer"184467...615"is22.8 ns - Latest JSON subset rerun recovered after the
take_while*hot-path cleanup —nullis16.5 ns,{"name":"oni-comb",...}is661.3 ns, and{"a":1,...,"h":8}is1,379 ns - Remaining hotspot —
flat_mapstill trailswinnow/nomon the smallest branch-dispatch microbenchmarks - See benchmark details and Japanese benchmark notes
use oni_comb_parser::prelude::*;
// Match 'a' or 'b'
let mut parser = char('a').or(char('b'));
let mut input = StrInputStream::new("b");
assert_eq!(parser.parse_next(&mut input).unwrap(), 'b');
// Identifier: letter/_ followed by alphanumeric/_
let mut input = StrInputStream::new("foo_123");
let (head, tail) = satisfy(|c: char| c.is_ascii_alphabetic() || c == '_')
.zip(take_while0(|c: char| c.is_ascii_alphanumeric() || c == '_'))
.parse_next(&mut input)
.unwrap();
assert_eq!(head, 'f');
assert_eq!(tail, "oo_123");
// Integer
let mut int_parser = take_while1(|c: char| c.is_ascii_digit())
.map(|s: &str| s.parse::<u64>().unwrap());
let mut input = StrInputStream::new("42");
assert_eq!(int_parser.parse_next(&mut input).unwrap(), 42);| Function | Description | Output |
|---|---|---|
char(c) |
Match a specific character | char |
tag(s) |
Match a specific string | &str |
satisfy(f) |
Match a character satisfying predicate | char |
take_while0(f) |
Consume 0+ matching characters | &str |
take_while1(f) |
Consume 1+ matching characters | &str |
eof() |
Match end of input | () |
whitespace0() / whitespace1() |
Consume ASCII whitespace | &str |
identifier() |
ASCII identifier [a-zA-Z_][a-zA-Z0-9_]* |
&str |
integer() |
Signed integer | i64 |
quoted_string() |
JSON-compliant double-quoted string (borrows when unescaped) | Cow<'a, str> |
escaped(open, close, esc, handler) |
Generic escaped string | String |
lexeme(p) |
Run parser then consume trailing whitespace | P::Output |
between(l, p, r) |
Run l, p, r and return p's value | P::Output |
predictive_choice() |
Select branch from next byte without consuming input | P::Output |
recursive(f) |
Build recursive parser | P::Output |
fn_parser(f) |
Wrap function as Parser | O |
| Method | Type Class | Description |
|---|---|---|
.map(f) |
Functor | Transform success value |
.zip(p) |
Applicative | Sequence two parsers, return pair |
.zip_left(p) |
Applicative | Run both, keep left |
.zip_right(p) |
Applicative | Run both, keep right |
.or(p) |
Alternative | Try right if left backtracks |
.flat_map(f) |
Monad | Context-sensitive branching |
.attempt() |
— | Downgrade Cut to Backtrack |
.cut() |
— | Upgrade Backtrack to Cut |
.optional() |
— | Convert Backtrack to None |
.many0() / .many1() |
— | Repeat 0+ / 1+ times |
.many0_fold(init, f) / .many1_fold(init, f) |
— | Fold 0+ / 1+ elements (zero-allocation) |
.many0_into(c) / .many1_into(c) |
— | Collect into custom Extend container |
.sep_by0(sep) / .sep_by1(sep) |
— | Separated repetition |
.sep_by0_fold(sep, init, f) / .sep_by1_fold(sep, init, f) |
— | Fold separated elements (zero-allocation) |
.sep_by0_into(sep, c) / .sep_by1_into(sep, c) |
— | Collect separated elements into custom container |
.chainl1(op) / .chainr1(op) |
— | Operator associativity chains |
.context(label) |
— | Add error context label |
.map_res(f, label) |
— | Transform with fallible function |
| Type | Token | Slice | Use Case |
|---|---|---|---|
StrInputStream<'a> |
char |
&'a str |
Text parsing (default) |
ByteInputStream<'a> |
u8 |
&'a [u8] |
Binary protocol parsing |
oni-comb-parser now satisfies the yaml-ready-parser acceptance contract at the parser-module level.
- The executable contract lives in
modules/parser/tests/yaml_ready_acceptance.rs - The litmus grammars cover block list, indent nesting, flow/block switching, multiline block, block scalar header, document boundary, simple-key gating, simple-key backtrack, flow plain scalar boundary, and indent error
- The contract is satisfied without introducing a YAML-specific Layout API into
modules/parser - Downstream YAML work is expected to compose the existing parser/core capabilities rather than patch capability gaps with direct
parse_next, directcheckpoint/reset, orfn_parser
cargo build -p oni-comb-parser
cargo test -p oni-comb-parser
# Benchmarks
cargo bench -p oni-comb-parser --bench comparisonDetailed benchmark tables and analysis:
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.