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}