jvm_descriptors/
method.rs

1use crate::r#type::Type;
2use chumsky::prelude::*;
3use std::{
4  fmt::{Display, Write},
5  str::FromStr,
6};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub enum Method {
10  Method {
11    name: String,
12    parameters: Vec<Type>,
13    return_type: Option<Type>,
14  },
15  Constructor {
16    parameters: Vec<Type>,
17  },
18}
19
20impl Display for Method {
21  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22    match self {
23      Method::Method {
24        name,
25        parameters,
26        return_type,
27      } => {
28        name.fmt(f)?;
29        f.write_char('(')?;
30        for parameter in parameters {
31          parameter.fmt(f)?;
32        }
33        f.write_char(')')?;
34        match return_type {
35          Some(ty) => ty.fmt(f),
36          None => f.write_char('V'),
37        }?;
38      }
39      Method::Constructor { parameters } => {
40        f.write_str("<init>")?;
41        f.write_char('(')?;
42        for parameter in parameters {
43          parameter.fmt(f)?;
44        }
45        f.write_char(')')?;
46        f.write_char('V')?;
47      }
48    }
49
50    Ok(())
51  }
52}
53
54impl FromStr for Method {
55  type Err = Vec<Simple<char>>;
56
57  fn from_str(s: &str) -> Result<Self, Self::Err> {
58    Self::parser().parse(s)
59  }
60}
61
62impl Method {
63  pub fn parser() -> impl Parser<char, Self, Error = Simple<char>> {
64    text::ident()
65      .then(Type::parser().repeated().delimited_by(just('('), just(')')))
66      .then(Type::parser().map(Some).or(just('V').to(None)))
67      .map(|((name, parameters), return_type)| Method::Method {
68        name,
69        parameters,
70        return_type,
71      })
72      .or(
73        just("<init>")
74          .ignore_then(Type::parser().repeated().delimited_by(just('('), just(')')))
75          .then_ignore(just('V'))
76          .map(|parameters| Method::Constructor { parameters }),
77      )
78  }
79}
80
81#[cfg(test)]
82mod tests {
83  use crate::class::Class;
84
85  use super::*;
86
87  #[test]
88  fn fmt() {
89    assert_eq!(
90      Method::Method {
91        name: "hello".to_string(),
92        parameters: vec![Type::Class(Class {
93          path: vec!["java".to_string(), "lang".to_string(), "String".to_string()],
94          subclasses: vec![]
95        })],
96        return_type: None,
97      }
98      .to_string(),
99      "hello(Ljava/lang/String;)V"
100    );
101
102    assert_eq!(
103      Method::Constructor {
104        parameters: vec![Type::Class(Class {
105          path: vec!["java".to_string(), "lang".to_string(), "String".to_string()],
106          subclasses: vec![]
107        })],
108      }
109      .to_string(),
110      "<init>(Ljava/lang/String;)V"
111    );
112  }
113
114  #[test]
115  fn parse() {
116    assert_eq!(
117      "hello(Ljava/lang/String;)V".parse(),
118      Ok(
119        Method::Method {
120          name: "hello".to_string(),
121          parameters: vec![Type::Class(Class {
122            path: vec!["java".to_string(), "lang".to_string(), "String".to_string()],
123            subclasses: vec![]
124          })],
125          return_type: None,
126        }
127        .to_string()
128      )
129    );
130
131    assert_eq!(
132      "<init>(Ljava/lang/String;)V".parse(),
133      Ok(Method::Constructor {
134        parameters: vec![Type::Class(Class {
135          path: vec!["java".to_string(), "lang".to_string(), "String".to_string()],
136          subclasses: vec![]
137        })],
138      })
139    );
140  }
141}