Add `filter_map` I think
This might be a skill issue, but I'm trying to create a lexer/parser combo, my tokenizer sees 1234 and spits out Token::Number(BigUint). Then I want my parser to see '-'? Number and spit out a Ast::Literal(BigInt). What should my parser look like? My first instinct was something like:
just(Token::Neg).or_not()
.then(any().filter(|tok| matches!(tok, Token::Number(_))))
.map(|(neg, num)| {
// Uh-oh, num is a Token::Number guaranteed, but I have to do something nasty like:
let number = match num { Token::Number(x) => x, _ => unreachable!() };
...
})
When I hit situations like this with Iterators, I reach for filter_map:
just(Token::Neg).or_not()
.then(any().filter_map(|tok| match tok { Token::Number(x) => Some(x), _ => None }))
.map(|(neg, num)| {
// Yay, num is a BigUint!
...
})
I can't find that particular combinator in chumsky. Am I missing it, or is it an oversight, or maybe it's not as simple as it looks? Or best of all, maybe there's a simpler way to do what I'm trying to do here? I suppose there's select...
I believe you are looking for chumsky::prelude::select (:
just(Token::Neg).or_not()
.then(select!(Token::Number(num) => num))
.map(|(neg, num)| {
if neg.is_some() {
Ast::Num(-num)
} else {
Ast::Num(num)
}
})
Ah ok that makes sense, thanks. I still feel like filter_map is worth adding because:
- It's paralleled in the
IteratorAPI - It doesn't use macros Admittedly the second point might be controversial, but I typically don't like relying on macros because they can make some language server features grumpy, and it's not always trivial to inline them. In other words: mumble mumble Rust macros bad.
In all seriousness, I'd be happy to PR this if it's a desired feature, otherwise feel free to close this out.
On second thought, primitive::select looks like filter_map, just named differently. This seems like it'd be a trivial change, but once again, I'd consider it useful simply for parity with Iterator.
I would be happy to see filter_map introduced, if anybody is interested in working on it. Behaviourally, it's strictly more useful than select! since it applies to any parser's output, not simply a single token of input.
We have try_map which can in practice be used for the same thing, but it's a little bit more noisy to use.
just now I was also thinking of this, I'll try implementing it