nom-parse-macros 0.4.2

Procedural macros for generating parser functions for the nom libary
Documentation
use crate::nom_packages::update_nom_expression;
use proc_macro2::Span;
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::{parse_quote, parse_quote_spanned, ExprBlock};

#[derive(Debug, PartialEq)]
pub enum ParseFormat {
    Match(syn::LitStr),
    Expr(syn::Expr, Vec<syn::WherePredicate>),
    Default,
}

impl Parse for ParseFormat {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        if input.is_empty() {
            Ok(ParseFormat::Default)
        } else if input.peek(syn::Token![match]) {
            let _keyword = input.parse::<syn::Token![match]>()?;
            let literal = input.parse::<syn::LitStr>()?;
            Ok(ParseFormat::Match(literal))
        } else {
            let expr = input.parse::<syn::Expr>()?;
            let mut extra_where_clauses = vec![];

            if input.peek(syn::Token![where]) {
                let where_clause = input.parse::<syn::WhereClause>()?;
                extra_where_clauses = where_clause.predicates.into_iter().collect();
            }

            Ok(ParseFormat::Expr(expr, extra_where_clauses))
        }
    }
}

impl ToTokens for ParseFormat {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self.to_expr() {
            Ok(expr) => expr.to_tokens(tokens),
            Err(err) => err.to_compile_error().to_tokens(tokens),
        }
    }
}

impl ParseFormat {
    fn to_expr(&self) -> syn::Result<syn::Expr> {
        match self {
            ParseFormat::Match(string) => generate_match_expression(string),
            ParseFormat::Expr(expr, _) => {
                let mut expr = expr.clone();
                update_nom_expression(&mut expr)?;
                Ok(expr)
            }
            ParseFormat::Default => {
                let mut expr: syn::Expr = parse_quote!(());
                update_nom_expression(&mut expr)?;
                Ok(expr)
            }
        }
    }
}

pub fn generate_match_expression(literal: &syn::LitStr) -> syn::Result<syn::Expr> {
    let value = literal.value();
    let mut block: ExprBlock = parse_quote!({});
    let statements = &mut block.block.stmts;
    let mut names = vec![];
    let mut first = true;

    for (index, part) in value.split("{}").enumerate() {
        if first {
            first = false;
        } else {
            let name = syn::Ident::new(&format!("field_{}", index), literal.span());
            statements.push(parse_quote_spanned! { literal.span() =>
                let (input, #name) = nom_parse_trait::ParseFrom::parse(input)?;
            });
            names.push(name);
        }

        if !part.is_empty() {
            let expr = generate_match_literal(part.as_bytes(), literal.span());
            statements.push(parse_quote_spanned! { literal.span() =>
                let (input, _) = #expr.parse(input)?;
            });
        }
    }

    statements.push(parse_quote_spanned! { literal.span() =>
        return Ok((input, (#(#names),*)));
    });

    Ok(parse_quote_spanned!( literal.span() => ( |input| #block )))
}

pub fn generate_match_literal(value: &[u8], span: Span) -> syn::Expr {
    let lit = syn::LitByteStr::new(value, span);
    parse_quote!(nom::bytes::complete::tag(#lit.as_ref()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use quote::ToTokens;

    #[test]
    fn test_match_expr() {
        let value = ParseFormat::Match(syn::LitStr::new("some {}", Span::call_site()));
        let expr = value.to_expr().unwrap();
        let expected: syn::Expr = parse_quote!(
            (|input| {
                let (input, _) = nom::bytes::complete::tag(b"some ".as_ref()).parse(input)?;
                let (input, field_1) = nom_parse_trait::ParseFrom::parse(input)?;
                return Ok((input, (field_1)));
            })
        );
        assert_eq!(expected, expr);
    }

    #[test]
    fn test_expr_expr() {
        let value = ParseFormat::Expr(parse_quote!(preceded("test", ())), vec![]);
        let expr = value.to_expr().unwrap();
        let expected: syn::Expr = parse_quote!(nom::sequence::preceded(
            nom::bytes::complete::tag(b"test".as_ref()),
            nom_parse_trait::ParseFrom::parse
        ));
        assert_eq!(expected, expr);
    }

    #[test]
    fn test_default_expr() {
        let value = ParseFormat::Default;
        let expr = value.to_expr().unwrap();
        let expected: syn::Expr = parse_quote!(nom_parse_trait::ParseFrom::parse);
        assert_eq!(expected, expr);
    }

    #[test]
    fn test_generate_parser_expr() {
        let value = "test {}{} test";
        let expr = generate_match_expression(&syn::LitStr::new(value, Span::call_site())).unwrap();
        let expected: syn::Expr = parse_quote!(
            (|input| {
                let (input, _) = nom::bytes::complete::tag(b"test ".as_ref()).parse(input)?;
                let (input, field_1) = nom_parse_trait::ParseFrom::parse(input)?;
                let (input, field_2) = nom_parse_trait::ParseFrom::parse(input)?;
                let (input, _) = nom::bytes::complete::tag(b" test".as_ref()).parse(input)?;
                return Ok((input, (field_1, field_2)));
            })
        );
        assert_eq!(expected, expr);
    }

    #[test]
    fn test_generate_match_literal() {
        let value = b"test\0\"!!";
        let span = Span::call_site();
        let expr: syn::Expr = generate_match_literal(value, span);
        assert_eq!(
            "nom :: bytes :: complete :: tag (b\"test\\0\\\"!!\" . as_ref ())",
            &expr.to_token_stream().to_string()
        );
    }
}