toml_edit 0.2.0

Yet another format-preserving TOML parser.
Documentation
use combine::char::{char, digit, hex_digit, oct_digit, string};
use combine::range::{range, recognize};
use combine::stream::RangeStream;
use combine::*;

// ;; Boolean

// boolean = true / false
parse!(boolean() -> bool, {
    choice((
        (char('t'), range("rue"),),
        (char('f'), range("alse"),),
    )).map(|p| p.0 == 't')
});

// ;; Integer

// integer = [ "-" / "+" ] int
// int = [1-9] 1*( DIGIT / _ DIGIT ) / DIGIT
// modified: int = 0 / [1-9] *( DIGIT / _ DIGIT )
parse!(parse_integer() -> &'a str, {
    recognize((
        optional(choice([char('-'), char('+')])),
        choice((
            char('0'),
            (
                satisfy(|c| '1' <= c && c <= '9'),
                skip_many((
                    optional(char('_')),
                    skip_many1(digit()),
                )),
            ).map(|t| t.0),
        )),
    ))
});

parse!(integer() -> i64, {
    choice!(
        attempt(parse_hex_integer()),
        attempt(parse_octal_integer()),
        attempt(parse_binary_integer()),
        parse_integer()
            .and_then(|s| s.replace("_", "").parse())
            .message("While parsing an Integer")
    )
});

// hex-int = "0x" HEXDIGIT *( HEXDIGIT / _ HEXDIGIT )
parse!(parse_hex_integer() -> i64, {
    string("0x").with(
        recognize((
            hex_digit(),
            skip_many((
                optional(char('_')),
                skip_many1(hex_digit()),
            )),
        ).map(|t| t.0)
    )).and_then(|s: &str| i64::from_str_radix(&s.replace("_", ""), 16))
       .message("While parsing a hexadecimal Integer")
});

// oct-int = "0o" digit0-7 *( digit0-7 / _ digit0-7 )
parse!(parse_octal_integer() -> i64, {
    string("0o").with(
        recognize((
            oct_digit(),
            skip_many((
                optional(char('_')),
                skip_many1(oct_digit()),
            )),
        ).map(|t| t.0)
    )).and_then(|s: &str| i64::from_str_radix(&s.replace("_", ""), 8))
       .message("While parsing an octal Integer")
});

// bin-int = "0b" digit0-1 *( digit0-1 / _ digit0-1 )
parse!(parse_binary_integer() -> i64, {
    string("0b").with(
        recognize((
            satisfy(|c: char| c.is_digit(0x2)),
            skip_many((
                optional(char('_')),
                skip_many1(satisfy(|c: char| c.is_digit(0x2))),
            )),
        ).map(|t| t.0)
    )).and_then(|s: &str| i64::from_str_radix(&s.replace("_", ""), 2))
       .message("While parsing a binary Integer")
});

// ;; Float

// frac = decimal-point zero-prefixable-int
// decimal-point = %x2E               ; .
// zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT )
parse!(frac() -> &'a str, {
    recognize((
        char('.'),
        skip_many1(digit()),
        skip_many((
            optional(char('_')),
            skip_many1(digit()),
        )),
    ))
});

// exp = e integer
// e = %x65 / %x45                    ; e E
parse!(exp() -> &'a str, {
    recognize((
        one_of("eE".chars()),
        parse_integer(),
    ))
});

// float = integer ( frac / ( frac exp ) / exp )
parse!(parse_float() -> &'a str, {
    recognize((
        attempt((parse_integer(), look_ahead(one_of("eE.".chars())))),
        choice((
            exp(),
            (
                frac(),
                optional(exp()),
            ).map(|_| "")
        )),
    ))
});

parse!(float() -> f64, {
    parse_float()
        .and_then(|s| s.replace("_", "").parse())
        .message("While parsing a Float")
});