chasa
chasa is a small, pragmatic parser-combinator library used by the YuLang project.
It is optimized for:
- Explicit backtracking control (
cut) and “rollback unless cut” - Separating success/failure from diagnostics (via
ErrorSink) - “Inline-friendly” combinator boundaries (aiming for near hand-written recursive descent after inlining)
Status: experimental; APIs may change.
What this crate looks like (in one sentence)
You build parsers as values, run them on an input, and get Option<Out> for success/failure — while diagnostics are collected separately in an ErrorSink.
Quick Start (for people who know nothing)
use chasa::prelude::*;- Use
"<input>".test(parser)for quick experiments. - If you need diagnostics, use
test_with_errors.
Minimal example
use *;
assert_eq!;
assert_eq!;
Mapping outputs (map, map_mut, map_once)
use *;
let parser = item.map;
assert_eq!;
Repetition (many / many1)
use *;
let out: = "aaab".test;
assert_eq!;
let out = "b".test;
assert_eq!;
Separated lists (sep, sep_map)
sep_map is often the most convenient API: you get an iterator and can count()/collect() etc.
use *;
let comma = to.skip;
let out: = "a,a,a".test;
assert_eq!;
Core concepts
Parsers are values
Parsers are plain values (structs / closures) implementing one of:
ParserOnce: run by value (consumesself)ParserMut: run with&mut self(stateful parsers)Parser: run with&self(reusable parsers)
Most combinators are available as methods on these traits (then_*, map_*, many_*, sep_*, ...).
Success/failure is Option
Some(out)= successNone= failure
This keeps the hot path minimal and makes “try a branch, roll it back” cheap.
Diagnostics go to ErrorSink
Errors and expectations are collected into an ErrorSink (e.g. LatestSink).
use *;
let = "b".test_with_errors;
assert_eq!;
assert!;
Useful tools:
label/label_with: add a human-readable expectationLatestSink: keep the latest merged error information (handy for “best effort” parsing)
Backtracking and cut (important!)
chasa is designed around a simple rule:
- If not cut, the input is rolled back on failure.
- If cut, failures become “committed” and callers do not roll back.
Use cut when you want to commit to a branch (e.g. after consuming a keyword) to avoid expensive backtracking and to keep errors local.
Related helpers:
cut: a parser that commits at its positioncut_after(p): commit afterpsucceedscut_on_ok(p): likecut_after, but intended for composing withthen-style flows
sep and separators
In many grammars, separators (commas, semicolons, etc.) are “structural”: you don’t want their value.
This crate models that by allowing separators to be SkipParser* in sep/sep_map*.
Internally, sep_map needs to unify &mut/& execution, so it uses a small adapter (RefOrMutSkipParser).
“Parsec mindset”: what to watch out for
If you come from Parsec (Haskell) or Parsec-like APIs:
-
Failure is
None, diagnostics are out-of-band.- In Parsec you often inspect an
Either/Resulterror value. - Here you use
ErrorSink(test_with_errors,label, etc.).
- In Parsec you often inspect an
-
Think “commit with
cut” rather than “sprinkletry”.- In Parsec,
tryexists because consumption may prevent backtracking. - Here, rollback is the default;
cutis how you stop rollback.
- In Parsec,
-
Be careful with zero-width success in loops.
- As with most combinator libraries,
many/sepcan loop forever if the inner parser succeeds without consuming input.
- As with most combinator libraries,
Compared to other Rust parser libraries (high-level)
This is not meant to be a strict benchmark; it’s a design summary.
Compared to nom
nomtends to be “byte-slice in / remainder out” withIResult.chasacenters aroundIn(checkpoint/rollback + error sink + cut state).cutexists in both worlds, butchasaaims for a consistent “rollback unless cut” story across combinators.
Compared to combine / other classic combinator libraries
- Similar style (combinators as values), but the library is tuned for YuLang’s needs:
- Separate error sink
- Strong focus on explicit commit points (
cut) - Very aggressive inlining intent
Why you might pick this crate
Concrete advantages (especially for language tooling):
- Explicit backtracking control (
cut) that influences rollback across combinators - Stateful parsing support (
ParserMut, plus env/local patterns viaIn) - Inlining-oriented design for “hand-written parser”-like performance profiles
Using from another project
This crate currently lives in the YuLang workspace. The easiest way to try it is a path dependency:
[]
= { = "/path/to/yulang/crates/chasa" }
Links
- Repository overview:
README.mdat the workspace root - API & examples: crate docs and doctests under
src/