java_class_parser 0.0.2

provides a way of easily parsing and analyzing java classes
Documentation
use nom::bytes::complete::{tag, take_till};

use nom::combinator::{eof, map};

use nom::multi::many0;
use nom::sequence::{delimited, preceded, tuple};
use nom::IResult;
use std::fmt::{Display, Formatter};

/// A signature
#[derive(Debug, PartialEq, Clone)]
#[allow(missing_docs)]
pub enum Signature<'a> {
    Boolean,
    Byte,
    Char,
    Short,
    Int,
    Long,
    Float,
    Double,
    Void,
    /// A fully qualified class
    FullyQualifiedClass(&'a str),
    /// An array of some type
    Array(Box<Signature<'a>>),
    /// A method
    Method {
        /// The args for the method
        args: Box<[Signature<'a>]>,
        /// The return type of the method
        ret_type: Box<Signature<'a>>,
    },
}

impl<'a> Signature<'a> {
    /// emits this signature as JNI
    pub fn jni(&self) -> String {
        match self {
            Signature::Boolean => "Z".to_string(),
            Signature::Byte => "B".to_string(),
            Signature::Char => "C".to_string(),
            Signature::Short => "S".to_string(),
            Signature::Int => "I".to_string(),
            Signature::Long => "J".to_string(),
            Signature::Float => "F".to_string(),
            Signature::Double => "D".to_string(),
            Signature::FullyQualifiedClass(f) => {
                format!("L{f};")
            }
            Signature::Array(array) => {
                format!("[{}", array.jni())
            }
            Signature::Method { args, ret_type } => {
                format!(
                    "({}){}",
                    args.iter().map(|s| s.jni()).collect::<String>(),
                    ret_type.jni()
                )
            }
            Signature::Void => "V".to_string(),
        }
    }

    /// Creates a new signature by parsing a string.
    pub fn new(str: &'a str) -> Result<Self, nom::Err<nom::error::Error<String>>> {
        let (bytes, parsed) =
            parse_signature(str).map_err(|e: nom::Err<nom::error::Error<&str>>| e.to_owned())?;
        eof(bytes).map_err(|e: nom::Err<nom::error::Error<&str>>| e.to_owned())?;
        Ok(parsed)
    }
}

impl Display for Signature<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Signature::Boolean => {
                write!(f, "boolean")
            }
            Signature::Byte => {
                write!(f, "byte")
            }
            Signature::Char => {
                write!(f, "char")
            }
            Signature::Short => {
                write!(f, "short")
            }
            Signature::Int => {
                write!(f, "int")
            }
            Signature::Long => {
                write!(f, "long")
            }
            Signature::Float => {
                write!(f, "float")
            }
            Signature::Double => {
                write!(f, "double")
            }
            Signature::FullyQualifiedClass(fqc) => {
                write!(f, "{}", fqc)
            }
            Signature::Array(array) => {
                write!(f, "{}[]", array)
            }
            Signature::Method { args, ret_type } => {
                write!(
                    f,
                    "{} ({})",
                    ret_type,
                    args.iter()
                        .map(|s| s.to_string())
                        .collect::<Vec<_>>()
                        .join(", ")
                )
            }
            Signature::Void => {
                write!(f, "void")
            }
        }
    }
}

fn parse_signature(string: &str) -> IResult<&str, Signature> {
    nom::branch::alt((
        map(tag("Z"), |_| Signature::Boolean),
        map(tag("B"), |_| Signature::Byte),
        map(tag("C"), |_| Signature::Char),
        map(tag("S"), |_| Signature::Short),
        map(tag("I"), |_| Signature::Int),
        map(tag("J"), |_| Signature::Long),
        map(tag("F"), |_| Signature::Float),
        map(tag("D"), |_| Signature::Double),
        map(tag("V"), |_| Signature::Void),
        map(
            delimited(tag("L"), take_till(|c| c == ';'), tag(";")),
            |chars| Signature::FullyQualifiedClass(chars),
        ),
        map(preceded(tag("["), parse_signature), |s| {
            Signature::Array(Box::new(s))
        }),
        map(
            tuple((
                delimited(tag("("), many0(parse_signature), tag(")")),
                parse_signature,
            )),
            |(args, ret_type)| Signature::Method {
                args: args.into_boxed_slice(),
                ret_type: Box::new(ret_type),
            },
        ),
    ))(string)
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn parse_jni_signature() {
        let jni = "(ZI)Ljava/lang/Object;";
        let parsed = Signature::new(jni).expect("couldn't parse");
        assert_eq!(
            parsed,
            Signature::Method {
                args: vec![Signature::Boolean, Signature::Int].into_boxed_slice(),
                ret_type: Box::new(Signature::FullyQualifiedClass("java/lang/Object"))
            }
        );
        assert_eq!(parsed.jni(), jni);
        assert_eq!(parsed.to_string(), "java/lang/Object (boolean, int)")
    }
}