Skip to main content

duchess_reflect/
argument.rs

1use proc_macro2::Span;
2
3use crate::{
4    class_info::{ClassDecl, ClassInfo, DotId, Id},
5    parse::{Parse, Parser},
6};
7
8pub struct DuchessDeclaration {
9    pub packages: Vec<JavaPackage>,
10}
11
12impl Parse for DuchessDeclaration {
13    fn parse(p: &mut Parser) -> syn::Result<Option<Self>> {
14        let packages = JavaPackage::parse_many(p)?;
15        Ok(Some(DuchessDeclaration { packages }))
16    }
17
18    fn description() -> String {
19        format!("list of classes whose methods you would like to call (e.g., `java.lang.Object`)")
20    }
21}
22
23/// There are various points where the user must select
24/// a method. In these cases, we permit them to either write
25/// just a class name (in which case we search for (hopefully) at most one
26/// such method), a class + method name, or a little mini class declaration
27/// that includes the full details (which accommodates the case where it is
28/// overloaded).
29pub enum MethodSelector {
30    /// User wrote `foo.bar.Baz`
31    ClassName(JavaPath),
32
33    /// User wrote `foo.bar.Baz::method`
34    MethodName(JavaPath, Ident),
35
36    /// User wrote `class Foo { ... }` with full details.
37    /// This class should have at most one member.
38    ClassInfo(ClassInfo),
39}
40
41impl MethodSelector {
42    /// Span for things that refer to the method
43    pub fn span(&self) -> Span {
44        match self {
45            MethodSelector::ClassName(jp) => jp.span,
46            MethodSelector::MethodName(_, ident) => ident.span,
47            MethodSelector::ClassInfo(ci) => ci.span,
48        }
49    }
50
51    /// Span for things that refer to the class the method is in
52    pub fn class_span(&self) -> Span {
53        match self {
54            MethodSelector::ClassName(jp) => jp.span,
55            MethodSelector::MethodName(jp, _) => jp.span,
56            MethodSelector::ClassInfo(ci) => ci.span,
57        }
58    }
59
60    pub fn class_name(&self) -> DotId {
61        match self {
62            MethodSelector::ClassName(c) => c.to_dot_id(),
63            MethodSelector::MethodName(c, _) => c.to_dot_id(),
64            MethodSelector::ClassInfo(_) => todo!(),
65        }
66    }
67
68    /// Returns the name of the method
69    pub fn method_name(&self) -> String {
70        match self {
71            MethodSelector::ClassName(_) => self.class_name().split().1.to_string(),
72            MethodSelector::MethodName(_, m) => m.to_string(),
73            MethodSelector::ClassInfo(_) => todo!(),
74        }
75    }
76}
77
78impl Parse for MethodSelector {
79    fn parse(p: &mut crate::parse::Parser) -> syn::Result<Option<Self>> {
80        // Check for a `class` declaration
81        if let Some(c) = ClassDecl::parse(p)? {
82            return match c {
83                ClassDecl::Reflected(r) => Err(syn::Error::new(
84                    r.span,
85                    format!("expected a class with a single member, not `*`"),
86                )),
87                ClassDecl::Specified(c) => {
88                    let members = c.constructors.len() + c.fields.len() + c.methods.len();
89                    if members != 1 {
90                        Err(syn::Error::new(
91                            c.span,
92                            format!(
93                                "expected a class with exactly one member, but {} members found",
94                                members
95                            ),
96                        ))
97                    } else {
98                        Ok(Some(MethodSelector::ClassInfo(c)))
99                    }
100                }
101            };
102        }
103
104        // Otherwise we expect either `foo.bar.Baz` or `foo.bar.Baz::method`
105        let Some(path) = JavaPath::parse(p)? else {
106            return Ok(None);
107        };
108
109        if let Some(_) = p.eat_punct(':') {
110            if let Some(_) = p.eat_punct(':') {
111                if let Some(ident) = Ident::parse(p)? {
112                    return Ok(Some(MethodSelector::MethodName(path, ident)));
113                }
114            }
115            Err(syn::Error::new(
116                p.peek_span().unwrap_or(Span::call_site()),
117                "expected method name after `::`",
118            ))
119        } else {
120            Ok(Some(MethodSelector::ClassName(path)))
121        }
122    }
123
124    fn description() -> String {
125        format!("method selector, e.g. `java.package.Class`, `java.package.Class::method`, or full details")
126    }
127}
128
129pub struct JavaPackage {
130    pub package_name: JavaPath,
131    pub classes: Vec<ClassDecl>,
132}
133
134impl Parse for JavaPackage {
135    fn parse(p: &mut Parser) -> syn::Result<Option<Self>> {
136        let Some(()) = p.eat_keyword("package") else {
137            return Ok(None);
138        };
139
140        let Some(package_name) = JavaPath::parse(p)? else {
141            return Err(syn::Error::new(
142                p.last_span().unwrap(),
143                "expected package name",
144            ));
145        };
146
147        let Some(_) = p.eat_punct(';') else {
148            return Err(syn::Error::new(
149                p.last_span().unwrap(),
150                "expected `;` after package name",
151            ));
152        };
153
154        let classes = ClassDecl::parse_many(p)?;
155
156        Ok(Some(JavaPackage {
157            package_name,
158            classes,
159        }))
160    }
161
162    fn description() -> String {
163        format!("java package to reflect (e.g., `package foo; ...`)")
164    }
165}
166
167pub struct JavaPath {
168    pub ids: Vec<Ident>,
169    pub span: Span,
170}
171
172impl JavaPath {
173    pub fn to_dot_id(&self) -> DotId {
174        self.ids.iter().map(|ident| ident.to_id()).collect()
175    }
176}
177
178impl Parse for JavaPath {
179    fn parse(p: &mut Parser) -> syn::Result<Option<Self>> {
180        let Some(text) = Ident::parse(p)? else {
181            return Ok(None);
182        };
183
184        let mut span = text.span;
185        let mut ids = vec![text];
186
187        while let Some(_) = p.eat_punct('.') {
188            let Some(next) = Ident::parse(p)? else {
189                return Err(syn::Error::new(
190                    p.last_span().unwrap(),
191                    format!("expected identifier after `.`"),
192                ));
193            };
194            span = span.join(next.span).unwrap_or(span);
195            ids.push(next);
196        }
197
198        Ok(Some(JavaPath { ids, span }))
199    }
200
201    fn description() -> String {
202        format!("java class name (e.g., `java.lang.Object`)")
203    }
204}
205
206impl std::fmt::Display for JavaPath {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        if let Some((id0, ids)) = self.ids.split_first() {
209            write!(f, "{}", id0)?;
210            for id in ids {
211                write!(f, ".{}", id)?;
212            }
213            Ok(())
214        } else {
215            Ok(())
216        }
217    }
218}
219
220pub struct Ident {
221    pub text: String,
222    pub span: Span,
223}
224
225impl Ident {
226    pub fn to_id(&self) -> Id {
227        Id::from(&self.text[..])
228    }
229}
230
231impl Parse for Ident {
232    fn parse(p: &mut Parser) -> syn::Result<Option<Self>> {
233        let Some(text) = p.eat_ident() else {
234            return Ok(None);
235        };
236
237        Ok(Some(Ident {
238            text,
239            span: p.last_span().unwrap(),
240        }))
241    }
242
243    fn description() -> String {
244        format!("Java identifier")
245    }
246}
247
248impl std::fmt::Display for Ident {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        write!(f, "{}", &self.text)
251    }
252}