Created
December 25, 2020 21:31
-
-
Save gbrls/5cf1d4b82bcf8efa8ac78d68df7ed354 to your computer and use it in GitHub Desktop.
Parser combinator examples in Rust using Nom
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// This is a small program to show how to use nom v6 crate to create parsers | |
/// and how to implement precedence with a parser combinator library. | |
/// Useful resources: | |
/// Nom's reference: https://docs.rs/nom/6.0.1/nom/index.html | |
/// 'Parsing with Nom' section of Gentle rust intro: https://stevedonovan.github.io/rust-gentle-intro/nom-intro.html (it uses an outdated version of nom) | |
/// Nom's github documentation: https://github.com/Geal/nom/blob/master/doc/making_a_new_parser_from_scratch.md | |
/// combinators explanations: https://github.com/Geal/nom/blob/master/doc/choosing_a_combinator.md | |
use nom::{character::complete::digit1, IResult}; | |
use std::str::FromStr; | |
#[macro_use] | |
extern crate nom; | |
// First iteration, here we just apply digit1. This shows the internals of nom. | |
// each parser is a function that takes a &str and resturns an IResult. | |
// The first part of the IResult holds the rest of the input. | |
// the second part of it holds the newly created output. | |
fn parse_dig1(i: &str) -> IResult<&str, &str> { | |
digit1(i) | |
} | |
named!(ignore_ws<&str, &str>, take_while!(|c: char| c.is_whitespace())); | |
// This is more "idiomatic nom", does the same as the previous and then converts the input to i32 | |
named!(number_i32<&str, i32>, map!(digit1, |i| i32::from_str(i).unwrap())); | |
named!(number<&str, i32>, preceded!(complete!(ignore_ws), number_i32)); | |
// The grammar for this arithimetic expression parser is the following: | |
// <term> ::= <factor> (('+' | '-') <factor>)* | |
// <factor> ::= <primary> (('*' | '/') <primary>)* | |
// <primary> ::= NUMBER | ( <term> ) | |
// As you can see, the implementation is really simple, being very close to the grammar. | |
named!(fold_sum<&str, i32>, | |
fold_many1!( | |
number, | |
0, | |
|acc, v| acc + v)); | |
named!(primary<&str, i32>, | |
alt!( | |
number | | |
delimited!( | |
char!('('), | |
term, | |
char!(')') | |
) | |
) | |
); | |
named!(factor<&str, i32>, | |
do_parse!( | |
first: primary >> | |
rest: fold_many0! ( | |
tuple!( | |
preceded!(complete!(ignore_ws), alt!(char!('*') | char!('/'))), | |
primary | |
), | |
first, | |
|acc, (_, v) | acc * v | |
) >> | |
(rest) | |
) | |
); | |
named!(term<&str, i32>, | |
do_parse!( | |
first: factor >> | |
rest: fold_many0! ( | |
tuple!( | |
preceded!(complete!(ignore_ws), alt!(char!('+') | char!('-'))), | |
factor | |
), | |
first, | |
|acc, (_, v) | acc + v | |
) >> | |
(rest) | |
) | |
); | |
fn main() {} | |
#[cfg(test)] | |
mod tests { | |
#[test] | |
fn parse_dig1() { | |
assert_eq!(super::parse_dig1("1234 12 a"), Ok((" 12 a", "1234"))); | |
} | |
#[test] | |
fn number_i32() { | |
assert_eq!(super::number_i32("1234 12 a"), Ok((" 12 a", 1234))); | |
} | |
#[test] | |
fn number() { | |
assert_eq!(super::number(" 1 2 3"), Ok((" 2 3", 1))); | |
} | |
#[test] | |
fn fold_sum() { | |
assert_eq!(super::fold_sum(" 30 12 5"), Ok(("", 47))); | |
} | |
#[test] | |
fn primary() { | |
assert_eq!(super::primary("(0)"), Ok(("", 0))); | |
assert_eq!(super::primary("0"), Ok(("", 0))); | |
} | |
#[test] | |
fn term() { | |
assert_eq!(super::term("(2 + 3) * 4"), Ok(("", 20))); | |
assert_eq!(super::term("2 * 3 * 4"), Ok(("", 24))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment