Skip to content

Instantly share code, notes, and snippets.

@gbrls
Created December 25, 2020 21:31
Show Gist options
  • Save gbrls/5cf1d4b82bcf8efa8ac78d68df7ed354 to your computer and use it in GitHub Desktop.
Save gbrls/5cf1d4b82bcf8efa8ac78d68df7ed354 to your computer and use it in GitHub Desktop.
Parser combinator examples in Rust using Nom
/// 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