22 breaking releases

0.23.0 Feb 7, 2025
0.21.0 Jan 28, 2025
0.16.0 Dec 29, 2024
0.14.0 Jun 27, 2024
0.6.0 Dec 19, 2023

#69 in Parser tooling

Download history 2/week @ 2024-10-30 1/week @ 2024-11-06 1/week @ 2024-12-04 1/week @ 2024-12-11 260/week @ 2024-12-25 6/week @ 2025-01-01 159/week @ 2025-01-08 255/week @ 2025-01-15 163/week @ 2025-01-22 137/week @ 2025-01-29 244/week @ 2025-02-05

801 downloads per month
Used in 2 crates

MIT license

27KB
507 lines

bparse

A combinatorial approach to matching slices, especially useful for writing lexers and tokenizers.

The crate borrows concepts from other parser-combinator crates but heavily simplifies things by eschewing error management and focusing exclusively on parsing byte slices.

Smol example:

Here is the code for a request cookie parser using bparse. Unlike other parser combinator libraries, bparse aims to integrate with the parsing code you'll write anyway, not take it over. In this example, it's just used to validate the cookie name and value.

Even in this small example, its use could be expanded: e.g. the reader has a parse_delimited method that could be used to handle double quoted cookie values. But no matter how much or little you make use of the crate, your code will remain imperative as the good lord intended.

use bparse::{digit, alpha, any, at_least, end, range, SliceReader, Pattern};

let cookies = parse_cookies(r#"Cookie1=Value; Cookie2="Value2""#).unwrap();
assert_eq!(cookies[0], ("Cookie1", "Value"));
assert_eq!(cookies[1], ("Cookie2", "Value2"));

let err = parse_cookies(r#"Cookie?=Value"#).unwrap_err();
assert_eq!(err, "malformed cookie: bad name");

fn parse_cookies(cookie_str: &str) -> Result<Vec<(&str, &str)>, &'static str> {
    let mut cookies = Vec::new();
    // cookie-name       = token
    let cookie_name = at_least(1, alpha().or(digit()));
    // cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
    let cookie_octet = "\x21"
        .or(range(0x23, 0x2b))
        .or(range(0x2d, 0x3a))
        .or(range(0x3c, 0x5b))
        .or(range(0x5d, 0x7e));
    // cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
    let cookie_value = "\""
        .then(any(cookie_octet))
        .then("\"")
        .or(any(cookie_octet));

    let cookie_str = cookie_str.trim_ascii();

    if cookie_str.is_empty() {
        return Ok(cookies);
    }

    let parts = cookie_str.trim_ascii().split("; ");

    for part in parts {
        let Some((key, value)) = part.split_once("=") else {
            return Err("malformed cookie: missing equals sign");
        };

        let mut key_reader = SliceReader::new(key.as_bytes());
        let mut value_reader = SliceReader::new(value.as_bytes());

        if !key_reader.accept(cookie_name.then(end())) {
            return Err("malformed cookie: bad name");
        }

        if !value_reader.accept(cookie_value.then(end())) {
            return Err("malformed cookie: bad value");
        }

        let trimmed = value
            .strip_prefix('"')
            .and_then(|s| s.strip_suffix('"'))
            .unwrap_or(value);

        cookies.push((key, trimmed));
    }

    Ok(cookies)
}

Bigger example: For a larger example, here is a complete http request parser using bparse.

No runtime deps