| Crates.io | chasa |
| lib.rs | chasa |
| version | 0.4.1 |
| created_at | 2022-03-01 11:01:33.118333+00 |
| updated_at | 2025-12-22 15:54:22.228648+00 |
| description | A parser combinator focused on rollback/commit, streaming inputs, and composable method chains. |
| homepage | |
| repository | https://github.com/momota1029/chasa |
| max_upload_size | |
| id | 541480 |
| size | 296,949 |
Parser combinators for the YuLang workspace.
use chasa::prelude::*;
// Parse "let x" and extract the identifier
let mut input = "let x";
let name = parse_ok_once(&mut input, tag("let").right(ws1).right(any)).unwrap();
assert_eq!(name, 'x');
Key combinators:
tag("...") – match an exact stringws1 / ws – match whitespace (one-or-more / zero-or-more)any – match any single characterright(q) – parse both, return right resultmany() – repeat zero or more timessep(s) – parse separated listMost combinators are available as methods (via ParserOnce / ParserMut / Parser), and the
prelude imports those traits so you can write p.right(q) / p.many() / p.sep(...).
This crate supports both combinator style and imperative style via In.
use chasa::prelude::*;
#[derive(Debug, PartialEq, Eq)]
enum SExp {
Atom(String),
List(Vec<SExp>),
}
fn sexp(mut i: In<&str>) -> Option<SExp> {
let atom_char = none_of(SPACE.and("()"));
let atom = atom_char.many1().map(SExp::Atom);
let list = sexp
.sep(ws1)
.map(SExp::List)
.between(ws, ws)
.between(item('('), item(')'));
i.choice((list, atom))
}
let mut input = "(a (b c) d)";
let out = parse_ok_once(&mut input, sexp).unwrap();
assert_eq!(
out,
SExp::List(vec![
SExp::Atom("a".into()),
SExp::List(vec![SExp::Atom("b".into()), SExp::Atom("c".into())]),
SExp::Atom("d".into()),
])
);
key = value (imperative style)use chasa::prelude::*;
#[derive(Debug, PartialEq, Eq)]
enum Value {
Bool(bool),
Number(i64),
Str(String),
}
fn kv(mut i: In<&str>) -> Option<(String, Value)> {
let ident = one_of(ASCII_ALPHA.and("_")).bind(|h| {
one_of(ASCII_ALPHANUM.and("_"))
.many_map(move |it| std::iter::once(h).chain(it).collect::<String>())
});
let eq = item('=').between(ws, ws);
let digit = one_of(ASCII_DIGIT);
let number = choice((
item('-').right(digit.many1::<String>()).map_once(|s: String| {
let n = s.parse::<i64>().unwrap();
Value::Number(-n)
}),
digit.many1::<String>().map_once(|s: String| {
let n = s.parse::<i64>().unwrap();
Value::Number(n)
}),
));
let str_body = none_of("\"\\").many::<String>();
let string = str_body.between(item('\"'), item('\"')).map(Value::Str);
let key = i.run(ident)?;
i.run(eq)?;
let value = i.choice((
tag("true").to(Value::Bool(true)),
tag("false").to(Value::Bool(false)),
number,
string,
))?;
Some((key, value))
}
let mut input = "port = 8080";
assert_eq!(
parse_ok_once(&mut input, kv).unwrap(),
("port".into(), Value::Number(8080))
);
let mut input = "name = \"alice\"";
assert_eq!(
parse_ok_once(&mut input, kv).unwrap(),
("name".into(), Value::Str("alice".into()))
);
Entry points:
parse_once(&mut input, parser) – run a ParserOnce with a fresh Mergerparse_ok_once(&mut input, parser) – run a ParserOnce and return Result<T, Error>Building blocks:
any, item(c), one_of("abc"), none_of("xyz")tag("keyword")ws, ws1then, right, left, betweenor, choicemany, many1, many_mapsep, sep1, sep_map, sep_reducelookahead, notcut, maybe, labelprelude: start here for importsparser: combinators and traitsinput: input abstractions and streaming inputsparse: helpers like parse_ok_once