How does nom::combinator::map_parser enable chaining multiple parser transformations in a single combinator?

map_parser applies a parser to the output of another parser, enabling you to transform parsed input through a second parsing stage—effectively chaining two parsers where the first parser's output becomes the second parser's input, all within a single combinator expression. This is distinct from map, which applies a pure function to a parser's output; map_parser applies a parser to a parser's output, allowing the transformation itself to consume input and produce structured results.

The map_parser Signature

use nom::{IResult, Parser};
 
// Simplified signature
pub fn map_parser<I, O1, O2, E, F, G>(
    parser: F,
    applied_parser: G,
) -> impl Fn(I) -> IResult<I, O2>
where
    F: Parser<I, O1, E>,
    G: Parser<O1, O2, E>,
{
    // Implementation:
    // 1. Apply first parser to input
    // 2. Get output O1 from first parser
    // 3. Apply second parser to O1 (not remaining input!)
    // 4. Return result from second parser
}
 
// Key insight: applied_parser takes O1 as input, not I
// The output of first parser becomes input to second parser

map_parser takes two parsers where the second parser operates on the first parser's output.

map vs map_parser

use nom::{IResult, bytes::complete::tag, character::complete::digit1, combinator::{map, map_parser}};
 
fn map_vs_map_parser() {
    let input = "123abc";
    
    // map: Apply a function to parser output
    // The function is pure - it doesn't parse
    let parse_with_map: fn(&str) -> IResult<&str, i32> = map(
        digit1,
        |digits: &str| digits.parse::<i32>().unwrap()
    );
    
    let (remaining, result) = parse_with_map(input).unwrap();
    // remaining = "abc"
    // result = 123 (i32)
    
    // map_parser: Apply a parser to parser output
    // The second argument is a parser, not a function
    let parse_with_map_parser: fn(&str) -> IResult<&str, &str> = map_parser(
        digit1,                           // First parser: parse digits
        |digits: &str| tag(digits)(digits) // Second parser: match those digits
    );
    
    // This example is contrived, but shows:
    // - digit1 parses "123"
    // - Second parser receives "123" as input (not "abc")
}

map applies a function to output; map_parser applies a parser to output.

Basic Example: Nested String Parsing

use nom::{IResult, bytes::complete::{tag, take_while}, character::complete::digit1, combinator::map_parser};
 
fn nested_parsing() {
    // Scenario: Parse a length-prefixed string
    // Format: <length><content>
    // Example: "5hello" -> parse "5" then take 5 characters
    
    fn parse_length_prefixed(input: &str) -> IResult<&str, &str> {
        // First: parse the length digits
        // Then: take that many characters from remaining input
        
        // This CANNOT be done with map_parser directly
        // because the second parser needs the REMAINING input, not the parsed digits
        
        // map_parser applies second parser to first's OUTPUT
        // We need different combinators for length-prefixed parsing
    }
    
    // But map_parser IS useful when:
    // The output of first parser should be parsed again
    
    // Example: Parse a string that contains another format
    let input = "\"(1,2,3)\"";  // A quoted tuple
    
    // Parse the outer quotes, then parse the tuple inside
    fn parse_quoted_tuple(input: &str) -> IResult<&str, Vec<i32>> {
        // This requires nested parsing where inner parser operates on output
        // Not directly map_parser, but shows the concept
    }
}

map_parser is useful when the second parser should operate on the first parser's output.

Parsing Parsed Output

use nom::{IResult, bytes::complete::{take, take_while}, character::complete::digit1, combinator::map_parser};
 
fn parse_parsed_output() {
    // Common use case: Parse a string representation
    
    // Example: Input contains "5:hello" where 5 is length
    // But let's say we parsed the length and content separately
    // and now want to re-parse the content
    
    let input = "abc123";
    
    // First parser: extract "123"
    // Second parser: parse "123" as digits
    let parser = map_parser(
        |i| take_while(|c| c.is_ascii_digit())(i),  // Take digits: "123"
        |out| {
            // This parser receives "123", not the original input
            // We could further parse it
            let (_remaining, parsed) = digit1(out)?;
            Ok(("", parsed))  // Return parsed result
        }
    );
    
    let (remaining, result) = parser(input).unwrap();
    // remaining from original: "abc"
    // result: "123"
}

The second parser receives the output of the first parser, not the remaining input.

Practical Example: JSON Escaped Strings

use nom::{IResult, bytes::complete::take_until, character::complete::char, combinator::map_parser};
 
fn parse_escaped_string() {
    // Parsing escaped content that needs transformation
    
    let input = r"hello\nworld";  // Contains escape sequence
    
    // First parse the raw content, then interpret escapes
    // This is where map_parser is useful: the escaped content
    // needs to be processed by another parser
    
    fn parse_escape_sequences(input: &str) -> IResult<&str, String> {
        // Parse escape sequences in the string
        let mut result = String::new();
        let mut chars = input.chars().peekable();
        
        while let Some(c) = chars.next() {
            if c == '\\' {
                if let Some(escaped) = chars.next() {
                    match escaped {
                        'n' => result.push('\n'),
                        't' => result.push('\t'),
                        'r' => result.push('\r'),
                        _ => result.push(escaped),
                    }
                }
            } else {
                result.push(c);
            }
        }
        
        Ok(("", result))
    }
    
    // Combine parsers using map_parser
    fn parse_escaped_content(input: &str) -> IResult<&str, String> {
        map_parser(
            take_until("\""),  // Take until end quote
            parse_escape_sequences,  // Process escapes in the content
        )(input)
    }
}

map_parser enables processing parsed content through a second parsing stage.

Chaining Multiple Transformations

use nom::{IResult, combinator::map_parser, bytes::complete::{take_while, take_while1}, character::complete::digit1};
 
fn chain_transformations() {
    // Chain multiple parsing stages
    
    let input = "abc123def";
    
    // First: extract digits "123"
    // Then: parse as number
    // (This is contrived but shows chaining)
    
    fn extract_digits(input: &str) -> IResult<&str, &str> {
        take_while1(|c: char| c.is_ascii_digit())(input)
    }
    
    fn parse_number(digits: &str) -> IResult<&str, i32> {
        let num: i32 = digits.parse().unwrap();
        Ok(("", num))
    }
    
    // This doesn't work with map_parser because:
    // - extract_digits returns remaining input AND digits
    // - map_parser passes ONLY the digits to second parser
    // - But we want to track remaining input
    
    // The correct use case for map_parser:
    // When the OUTPUT (not remaining input) needs further parsing
}

map_parser chains when the output itself needs parsing, not when continuing from remaining input.

Real Use Case: Token Parsing

use nom::{IResult, bytes::complete::take_while1, character::complete::{digit1, alpha1}, combinator::map_parser, branch::alt};
 
fn token_parsing() {
    // Parse tokens that may have sub-structure
    
    // Example: "123" is a number token, parse it fully
    // Example: "abc" is an identifier token, check keywords
    
    // First pass: tokenize
    // Second pass: classify/interpret tokens
    
    // Token types
    #[derive(Debug, PartialEq)]
    enum Token {
        Number(i64),
        Identifier(String),
        Keyword(String),
    }
    
    // Keywords
    fn classify_identifier(ident: &str) -> IResult<&str, Token> {
        let token = match ident {
            "if" | "then" | "else" | "let" | "fn" => Token::Keyword(ident.to_string()),
            _ => Token::Identifier(ident.to_string()),
        };
        Ok(("", token))
    }
    
    // Parse a single token
    fn parse_token(input: &str) -> IResult<&str, Token> {
        alt((
            // Number: parse digits, then convert to i64
            map_parser(
                digit1,
                |digits: &str| {
                    let num: i64 = digits.parse().unwrap();
                    Ok(("", Token::Number(num)))
                }
            ),
            // Identifier: parse alphabetic, then classify
            map_parser(
                alpha1,
                classify_identifier,
            ),
        ))(input)
    }
    
    let (_, tok1) = parse_token("123").unwrap();
    assert_eq!(tok1, Token::Number(123));
    
    let (_, tok2) = parse_token("if").unwrap();
    assert_eq!(tok2, Token::Keyword("if".to_string()));
    
    let (_, tok3) = parse_token("foo").unwrap();
    assert_eq!(tok3, Token::Identifier("foo".to_string()));
}

map_parser is useful for token classification where the first parser extracts and the second interprets.

Nested Format Parsing

use nom::{IResult, bytes::complete::{tag, take_until, take_while_m_n}, combinator::map_parser};
 
fn nested_format_parsing() {
    // Parse nested or encoded formats
    
    // Example: Base64-encoded content inside delimiters
    // First: extract content between delimiters
    // Second: decode and parse
    
    fn parse_delimited_base64(input: &str) -> IResult<&str, String> {
        // Take content until end delimiter
        map_parser(
            |i| {
                let (i, _) = tag("[[")(i)?;
                let (i, content) = take_until("]]")(i)?;
                let (i, _) = tag("]]")(i)?;
                Ok((i, content))
            },
            |encoded: &str| {
                // Decode base64 and parse
                // (simplified - actual base64 decoding would be here)
                let decoded = encoded.to_uppercase();  // Pretend this is decoding
                Ok(("", decoded))
            }
        )(input)
    }
    
    let (_, result) = parse_delimited_base64("[[aGVsbG8=]]").unwrap();
    // Result is decoded content
}

map_parser enables parsing extracted content through a second parsing stage.

Combining with Other Combinators

use nom::{IResult, bytes::complete::tag, character::complete::{digit1, alpha1}, combinator::map_parser, sequence::preceded, multi::many0};
 
fn combine_combinators() {
    let input = "key:123";
    
    // Parse key-value pair where value is parsed further
    
    // First: parse "key:123" extracting "123"
    // Second: parse "123" as digits
    
    fn parse_key_value(input: &str) -> IResult<&str, (&str, i64)> {
        let (input, key) = alpha1(input)?;
        let (input, _) = tag(":")(input)?;
        
        // Now we have value in input, but let's show map_parser usage
        // Actually this case is better served by map, not map_parser
        
        // map_parser is useful when the VALUE itself needs parsing
        // Like parsing escaped content, encoded content, etc.
    }
    
    // Better example: parse multiple nested items
    let input = "123 456 789";
    
    fn parse_number_list(input: &str) -> IResult<&str, Vec<i64>> {
        // This is simpler with map, but shows concept
        map_parser(
            |i| take_while(|c| c.is_ascii_digit() || c == ' ')(i),
            |nums: &str| {
                let result: Vec<i64> = nums
                    .split_whitespace()
                    .filter_map(|s| s.parse().ok())
                    .collect();
                Ok(("", result))
            }
        )(input)
    }
}

map_parser combines with other combinators for complex parsing scenarios.

The Parser Trait and map_parser

use nom::{IResult, Parser, bytes::complete::tag, combinator::map_parser};
 
fn parser_trait() {
    // map_parser works with any types implementing Parser
    
    // The Parser trait:
    // trait Parser<I, O, E> {
    //     fn parse(&mut self, input: I) -> IResult<I, O, E>;
    // }
    
    // map_parser signature (conceptual):
    // fn map_parser<I, O1, O2, E, P1, P2>(p1: P1, p2: P2) -> impl Parser<I, O2, E>
    // where
    //     P1: Parser<I, O1, E>,
    //     P2: Parser<O1, O2, E>,  // Note: O1, not I
    // 
    
    // The key difference: P2 takes O1 as input
    // This is why map_parser is different from flat_map:
    // - flat_map: second parser takes REMAINING input from first parser
    // - map_parser: second parser takes OUTPUT from first parser
    
    let input = "hello";
    
    let parser = map_parser(
        tag("hello"),
        |output| {
            // output is "hello", not remaining input
            // This parser operates on "hello"
            tag("hello")(output)  // This matches since output is "hello"
        }
    );
}

The key distinction: the second parser operates on the output, not the remaining input.

map_parser vs flat_map

use nom::{IResult, bytes::complete::{tag, take}, character::complete::digit1, combinator::{map_parser, flat_map}};
 
fn map_parser_vs_flat_map() {
    let input = "5hello";
    
    // flat_map: Second parser takes REMAINING input
    let parse_length_prefixed_flat = flat_map(
        digit1,                              // Parse length: "5"
        |len_str| {
            let len: usize = len_str.parse().unwrap();
            take(len)                        // Take that many chars from REMAINING input
        }
    );
    
    let (remaining, result) = parse_length_prefixed_flat(input).unwrap();
    // remaining = ""
    // result = "hello" (took 5 chars from "hello")
    
    // map_parser: Second parser takes OUTPUT
    let parse_output_transform = map_parser(
        digit1,                              // Parse: "5"
        |digits: &str| {
            // This receives "5" as input
            // NOT "hello" (remaining input)
            tag(digits)(digits)              // Matches "5" against "5"
        }
    );
    
    let (remaining, result) = parse_output_transform("5hello").unwrap();
    // remaining = "hello" (from first parser)
    // result = "5" (from second parser operating on "5")
    
    // Use flat_map when: second parser needs remaining input
    // Use map_parser when: second parser needs first parser's output
}

flat_map passes remaining input to the second parser; map_parser passes the output.

Multiple Chained map_parser Calls

use nom::{IResult, bytes::complete::{tag, take}, character::complete::digit1, combinator::map_parser};
 
fn chained_map_parser() {
    // Chain multiple transformations
    
    // Example: Parse delimited content, then decode, then parse
    
    fn stage1_parse_delimited(input: &str) -> IResult<&str, &str> {
        let (input, _) = tag("{")(input)?;
        let (input, content) = take_while(|c| c != '}')(input)?;
        let (input, _) = tag("}")(input)?;
        Ok((input, content))
    }
    
    fn stage2_decode(content: &str) -> IResult<&str, String> {
        // Decode step (simplified)
        let decoded = content.to_uppercase();
        Ok(("", decoded))
    }
    
    fn stage3_parse_decoded(decoded: &str) -> IResult<&str, Vec<&str>> {
        // Parse into tokens
        let tokens: Vec<&str> = decoded.split(',').collect();
        Ok(("", tokens))
    }
    
    // Chain with map_parser (note: this requires careful input/output types)
    // In practice, this pattern uses more explicit combination
    
    // More practical: one map_parser per transformation stage
    let input = "{abc,def,ghi}";
    
    let parser = |i| {
        let (i, content) = stage1_parse_delimited(i)?;
        let (_, decoded) = stage2_decode(content)?;
        let (_, tokens) = stage3_parse_decoded(&decoded)?;
        Ok((i, tokens))
    };
}

Multiple transformation stages can be chained, though explicit sequencing is often clearer.

Practical Use Case: URL Parsing

use nom::{IResult, bytes::complete::{tag, take_while}, character::complete::alphanumeric1, combinator::map_parser};
 
fn url_parsing() {
    // Parse URLs with encoded components
    
    // Example: "/path%2Fto%2Ffile"
    // First: extract the whole path
    // Second: decode percent-encoding
    
    fn decode_percent(input: &str) -> IResult<&str, String> {
        let mut result = String::new();
        let mut chars = input.chars().peekable();
        
        while let Some(c) = chars.next() {
            if c == '%' {
                // Read next two hex digits
                if let (Some(h1), Some(h2)) = (chars.next(), chars.next()) {
                    if let Ok(byte) = u8::from_str_radix(&format!("{}{}", h1, h2), 16) {
                        result.push(byte as char);
                        continue;
                    }
                }
            }
            result.push(c);
        }
        
        Ok(("", result))
    }
    
    fn parse_encoded_path(input: &str) -> IResult<&str, String> {
        map_parser(
            take_while(|c| c != ' ' && c != '?'),  // Take path
            decode_percent,                          // Decode it
        )(input)
    }
    
    let (_, decoded) = parse_encoded_path("/path%2Fto%2Ffile").unwrap();
    // decoded = "/path/to/file"
}

map_parser is useful when parsed content needs decoding or further interpretation.

Error Handling with map_parser

use nom::{IResult, error::{Error, ErrorKind}, bytes::complete::tag, combinator::map_parser};
 
fn error_handling() {
    // map_parser propagates errors from both parsers
    
    let input = "hello";
    
    // First parser succeeds, second fails
    let result: IResult<&str, &str> = map_parser(
        tag("hello"),                        // Succeeds with "hello"
        |out| tag("world")(out),             // Fails: "hello" != "world"
    )(input);
    
    assert!(result.is_err());
    
    // Error from second parser:
    // Error { input: "hello", kind: Tag }
    
    // Both parser errors are preserved in the chain
    // Custom error types can be used for better diagnostics
    
    use nom::error::VerboseError;
    
    let result: IResult<&str, &str, VerboseError<&str>> = map_parser(
        tag("hello"),
        |out| {
            let res: IResult<&str, &str, VerboseError<&str>> = tag("world")(out);
            res
        },
    )(input);
    
    // VerboseError provides context about where parsing failed
}

Errors from both parsers are propagated; custom error types provide better diagnostics.

Performance Considerations

use nom::{IResult, bytes::complete::take, character::complete::digit1, combinator::{map, map_parser}};
 
fn performance() {
    // map_parser can have performance implications
    
    // 1. The second parser processes the output of first parser
    //    If output is large, this can be expensive
    
    // 2. Output often creates new allocations
    //    String copying, vec allocation, etc.
    
    // 3. For simple transformations, map is faster
    //    No parsing, just function application
    
    // Use map_parser when:
    // - The output needs parsing (not just transformation)
    // - The output format differs from input format
    // - Multiple parsing stages are needed
    
    // Use map when:
    // - Simple data transformation
    // - No input consumption needed
    // - Performance critical
    
    // Example: Fast number parsing
    fn fast_number(input: &str) -> IResult<&str, i64> {
        map(digit1, |digits| digits.parse().unwrap())(input)
    }
    
    // Example: Slower but more powerful
    fn parsed_number(input: &str) -> IResult<&str, i64> {
        map_parser(
            digit1,
            |digits| {
                // Could do complex validation here
                let num: i64 = digits.parse().unwrap();
                Ok(("", num))
            }
        )(input)
    }
    
    // For simple cases, prefer map
}

For simple transformations, map is more efficient; map_parser is for when parsing is needed.

Complete Example: Configuration Parser

use nom::{IResult, bytes::complete::{tag, take_until, take_while}, character::complete::{alpha1, digit1}, combinator::map_parser, sequence::separated_pair, branch::alt};
 
fn configuration_parser() {
    // Parse configuration format: "key: [base64_value] decoded: value"
    
    #[derive(Debug)]
    struct Config {
        key: String,
        value: String,
    }
    
    // Decode a simple encoding (ROT13 for example)
    fn decode_rot13(input: &str) -> IResult<&str, String> {
        let decoded: String = input.chars().map(|c| {
            if c.is_ascii_alphabetic() {
                let base = if c.is_ascii_lowercase() { b'a' } else { b'A' };
                ((c as u8 - base + 13) % 26 + base) as char
            } else {
                c
            }
        }).collect();
        Ok(("", decoded))
    }
    
    fn parse_config(input: &str) -> IResult<&str, Config> {
        let (input, key) = alpha1(input)?;
        let (input, _) = tag(": ")(input)?;
        let (input, encoded) = take_until("\n")(input)?;
        
        // Decode the encoded value using map_parser
        let (_, value) = decode_rot13(encoded)?;
        
        Ok((input, Config {
            key: key.to_string(),
            value,
        }))
    }
    
    // Or using map_parser style:
    fn parse_config_with_map_parser(input: &str) -> IResult<&str, Config> {
        map_parser(
            // First: parse key:encoded_value
            |i| {
                let (i, key) = alpha1(i)?;
                let (i, _) = tag(": ")(i)?;
                let (i, encoded) = take_until("\n")(i)?;
                Ok((i, (key, encoded)))
            },
            // Second: decode and create struct
            |(key, encoded)| {
                let (_, decoded) = decode_rot13(encoded)?;
                Ok(("", Config {
                    key: key.to_string(),
                    value: decoded,
                }))
            }
        )(input)
    }
    
    let input = "name:uryyb\n";  // "uryyb" is "hello" in ROT13
    let (_, config) = parse_config_with_map_parser(input).unwrap();
    assert_eq!(config.key, "name");
    assert_eq!(config.value, "hello");
}

A complete example showing map_parser for decoding parsed content.

Summary Table

fn summary() {
    // | Combinator | Second Stage | Input Type | Use Case |
    // |------------|--------------|------------|----------|
    // | map | Function | Output value | Transform output |
    // | map_parser | Parser | Output value | Parse output |
    // | flat_map | Parser | Remaining input | Continue parsing |
    
    // | Stage | Input | Output |
    // |-------|-------|--------|
    // | First parser | Original input | (remaining, output) |
    // | map_parser second parser | First's output | (remaining2, final) |
    // | flat_map second parser | First's remaining | (remaining2, final) |
    
    // | When to use | Example |
    // |--------------|---------|
    // | map | digit1 -> parse to number |
    // | map_parser | Extract delimited -> decode -> parse |
    // | flat_map | Parse length -> take N chars |
}

Synthesis

Quick reference:

use nom::combinator::map_parser;
 
// map_parser: Apply parser to output of another parser
let parser = map_parser(
    first_parser,   // Parser that produces output O1
    second_parser,  // Parser that takes O1 as input, produces O2
);
 
// Key difference from flat_map:
// - flat_map: second parser receives remaining input
// - map_parser: second parser receives output value
 
// Use map_parser when:
// 1. First parser extracts content
// 2. That content needs further parsing/decoding
// 3. The second parser operates on extracted content
 
// Use map when:
// 1. Output just needs transformation
// 2. No parsing needed
 
// Use flat_map when:
// 1. Second parser needs remaining input
// 2. Length-prefixed or conditional parsing

Key insight: map_parser enables a two-stage parsing pattern where the first parser's output becomes the second parser's input—distinct from map (which applies a function) and flat_map (which passes remaining input). This is particularly useful for parsing nested formats, encoded content, or when extracted data needs interpretation. The second parser operates on the structured output of the first, allowing transformations that require full parsing semantics rather than simple functions. Use map_parser when you need to parse-then-parse (extract content, then interpret it), use map for parse-then-transform (extract content, then apply function), and use flat_map for parse-dependent continuation (extract something, then use it to determine how to parse the remaining input).