chumsky icon indicating copy to clipboard operation
chumsky copied to clipboard

Add `filter_map` I think

Open jnhyatt opened this issue 2 months ago • 5 comments

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...

jnhyatt avatar Oct 27 '25 21:10 jnhyatt

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)
        }
    })

Zij-IT avatar Oct 27 '25 21:10 Zij-IT

Ah ok that makes sense, thanks. I still feel like filter_map is worth adding because:

  • It's paralleled in the Iterator API
  • 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.

jnhyatt avatar Oct 27 '25 21:10 jnhyatt

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.

jnhyatt avatar Oct 27 '25 21:10 jnhyatt

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.

zesterer avatar Oct 27 '25 23:10 zesterer

just now I was also thinking of this, I'll try implementing it

zeichenreihe avatar Dec 06 '25 22:12 zeichenreihe