jvm_descriptors/
class.rs

1use chumsky::prelude::*;
2use std::{
3  fmt::{Display, Write},
4  str::FromStr,
5};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct Class {
9  pub path: Vec<String>,
10  pub subclasses: Vec<String>,
11}
12
13impl Display for Class {
14  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15    self.path.join("/").fmt(f)?;
16    for subclass in &self.subclasses {
17      f.write_char('$')?;
18      subclass.fmt(f)?;
19    }
20
21    Ok(())
22  }
23}
24
25impl FromStr for Class {
26  type Err = Vec<Simple<char>>;
27
28  fn from_str(s: &str) -> Result<Self, Self::Err> {
29    Self::parser().parse(s)
30  }
31}
32
33impl Class {
34  pub fn parser() -> impl Parser<char, Self, Error = Simple<char>> {
35    text::ident()
36      .separated_by(just('/'))
37      .then(just('$').ignore_then(text::ident()).repeated())
38      .map(|(path, subclasses)| Class { path, subclasses })
39  }
40}
41
42#[cfg(test)]
43mod tests {
44  use super::*;
45
46  #[test]
47  fn fmt() {
48    assert_eq!(
49      Class {
50        path: vec!["java".to_string(), "lang".to_string(), "Object".to_string()],
51        subclasses: vec![],
52      }
53      .to_string(),
54      "java/lang/Object"
55    );
56
57    assert_eq!(
58      Class {
59        path: vec!["com".to_string(), "example".to_string(), "Foo".to_string()],
60        subclasses: vec!["Bar".to_string(), "Baz".to_string()],
61      }
62      .to_string(),
63      "com/example/Foo$Bar$Baz"
64    );
65  }
66
67  #[test]
68  fn parse() {
69    assert_eq!(
70      "java/lang/Object".parse(),
71      Ok(Class {
72        path: vec!["java".to_string(), "lang".to_string(), "Object".to_string()],
73        subclasses: vec![],
74      })
75    );
76
77    assert_eq!(
78      "com/example/Foo$Bar$Baz".parse(),
79      Ok(Class {
80        path: vec!["com".to_string(), "example".to_string(), "Foo".to_string()],
81        subclasses: vec!["Bar".to_string(), "Baz".to_string()],
82      })
83    );
84  }
85}