Skip to main content

java_jni_extras_proc/
lib.rs

1use std::fmt::Display;
2
3use proc_macro2::{Delimiter, TokenStream};
4use quote::quote;
5use syn::punctuated::Punctuated;
6use syn::{Ident, LitStr, Token, parse_macro_input};
7
8pub(crate) mod parse;
9pub(crate) mod process;
10pub(crate) mod output;
11
12pub(crate) mod kw {
13    use syn::custom_keyword;
14
15    custom_keyword!(void);
16    custom_keyword!(boolean);
17    custom_keyword!(byte);
18    custom_keyword!(char);
19    custom_keyword!(short);
20    custom_keyword!(int);
21    custom_keyword!(long);
22    custom_keyword!(float);
23    custom_keyword!(double);
24    custom_keyword!(native);
25    custom_keyword!(throw);
26    custom_keyword!(new);
27    custom_keyword!(import);
28    custom_keyword!(package);
29    custom_keyword!(class);
30
31    custom_keyword!(alloc);
32}
33
34#[derive(PartialEq)]
35pub(crate) enum JavaType {
36    Void,
37    Boolean,
38    Byte,
39    Char,
40    Short,
41    Int,
42    Long,
43    Float,
44    Double,
45    String,
46    Object(DottedName),
47    Array(Box<Self>),
48}
49
50#[derive(Clone)]
51pub(crate) struct DottedName(Punctuated<Ident, Token![.]>);
52
53impl PartialEq for DottedName {
54    fn eq(&self, other: &Self) -> bool {
55        self.0.len() == other.0.len() && (
56            self.0
57                .iter()
58                .zip(other.0.iter())
59                .all(|(a, b)| a == b)
60        )
61    }
62}
63
64impl Display for DottedName {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        let n = self.0
67            .iter()
68            .map(|t| t.to_string())
69            .collect::<Vec<_>>()
70            .join(".");
71        write!(f, "{}", n)
72    }
73}
74
75pub(crate) struct JavaClassDef {
76    pub(crate) package: DottedName,
77    pub(crate) imports: Vec<JavaImport>,
78    pub(crate) uses: Vec<JavaUse>,
79    pub(crate) class: JavaClass,
80    pub(crate) errors: Vec<TokenStream>,
81}
82
83pub(crate) struct JavaImport {
84    pub(crate) item: DottedName,
85}
86
87pub(crate) struct JavaUse {
88    pub(crate) rust_path: PathSepName,
89    pub(crate) java_path: DottedName,
90}
91
92#[derive(Default, Clone, Copy)]
93pub(crate) enum PathSepPrefix {
94    #[default]
95    None,
96    This,
97    Super,
98    Crate,
99}
100
101#[derive(Clone)]
102pub(crate) struct PathSepName {
103    pub(crate) prefix: PathSepPrefix,
104    pub(crate) parts: Vec<Ident>,
105}
106
107pub(crate) struct JavaClass {
108    pub(crate) name: Ident,
109    pub(crate) body: ClassBody,
110}
111
112pub(crate) struct ClassBody {
113    pub(crate) fields: Vec<JavaField>,
114    pub(crate) bindings: Vec<JavaBinding>,
115    pub(crate) natives: Vec<NativeFunction>,
116    pub(crate) errors: Vec<TokenStream>,
117}
118
119pub(crate) struct JavaField {
120    pub(crate) static_tk: Option<Token![static]>,
121    pub(crate) name: Ident,
122    pub(crate) typ: JavaType,
123}
124
125pub(crate) struct JavaBinding {
126    pub(crate) static_tk: Option<Token![static]>,
127    pub(crate) signature: JavaFnSignature,
128}
129
130pub(crate) struct NativeFunction {
131    pub(crate) static_tk: Option<Token![static]>,
132    pub(crate) signature: JavaFnSignature,
133    pub(crate) body: JavaCodeBlock,
134}
135
136pub(crate) struct JavaFnSignature {
137    pub(crate) fn_tk: Token![fn],
138    pub(crate) name: Ident,
139    pub(crate) params: Punctuated<JavaParam, Token![,]>,
140    pub(crate) arrow: Option<Token![->]>,
141    pub(crate) returns: JavaType,
142}
143
144pub(crate) struct JavaParam {
145    pub(crate) name: Ident,
146    pub(crate) typ: JavaType,
147}
148
149pub(crate) struct JavaCodeBlock {
150    pub(crate) delimiter: Delimiter,
151    pub(crate) sections: Vec<CodeSection>,
152}
153
154pub(crate) enum CodeSection {
155    Tokens(TokenStream),
156    Block(JavaCodeBlock),
157    Java(JavaExpression),
158}
159
160/// Matches:
161///  <expr>
162///  #Class::field #["..."] = ...;
163///  #obj.field #["..."] = ...;
164///  #arr[...] #["..."] = ...;
165///  #throw new Error("...");
166///  #throw ...;
167pub(crate) enum JavaExpression {
168    Expr(JavaExpr),
169    StaticFieldWrite(JavaStaticFieldWrite),
170    FieldWrite(JavaFieldWrite),
171    ArrayWrite(JavaArrayWrite),
172    ThrowNew(JavaThrowNew),
173    ThrowObj(JavaThrowObj),
174}
175
176/// Matches:
177///  #Class::func(...) #["..."]
178///  #obj.method(...) #["..."]
179///  #Class::field #["..."]
180///  #obj.field #["..."]
181///  #arr[...] #["..."]
182///  #new Class(...) #["..."]
183///  #alloc Class()
184///  (error) #...;
185pub(crate) enum JavaExpr {
186    Error(TokenStream),
187    StaticCall(JavaStaticCall),
188    MethodCall(JavaMethodCall),
189    StaticFieldRead(JavaStaticFieldRead),
190    FieldRead(JavaFieldRead),
191    ArrayRead(Box<JavaArrayRead>),
192    New(JavaNew),
193    Alloc(JavaAlloc),
194}
195
196/// Matches:
197///  <expr>
198///  or
199///  {...}
200///  or
201///  <single block-expression>
202pub(crate) enum JavaValueExpr {
203    Expr(JavaExpr),
204    Block(JavaCodeBlock),
205}
206
207/// matches function and field signatures:
208/// #["V(Ljava/lang/String;)"]
209/// #["Z"]
210pub(crate) struct JavaSignatureTag(LitStr);
211
212/// #Unknown::func(...) #["V(Ljava/lang/String;IIF)"]
213pub(crate) struct JavaStaticCall {
214    pub(crate) class: DottedName,
215    pub(crate) func: Ident,
216    pub(crate) args: Punctuated<JavaValueExpr, Token![,]>,
217    /// May be None if the function has no args and the return type can be inferred or is void.
218    pub(crate) signature: Option<JavaSignatureTag>,
219}
220
221/// #unknown.method(...) #["V(Ljava/lang/String;FF)"]
222pub(crate) struct JavaMethodCall {
223    pub(crate) obj: Ident,
224    pub(crate) func: Ident,
225    pub(crate) args: Punctuated<JavaValueExpr, Token![,]>,
226    /// May be None if the function has no args and the return type can be inferred or is void.
227    pub(crate) signature: Option<JavaSignatureTag>,
228}
229
230/// #Class::x #["J"]
231pub(crate) struct JavaStaticFieldRead {
232    pub(crate) class: DottedName,
233    pub(crate) field: Ident,
234    /// only needed if the type can't be inferred.
235    pub(crate) signature: Option<JavaSignatureTag>,
236}
237
238/// #Class::x #["Ljava/lang/String;"] = ...;
239pub(crate) struct JavaStaticFieldWrite {
240    pub(crate) class: DottedName,
241    pub(crate) field: Ident,
242    /// only needed if the type can't be inferred.
243    pub(crate) signature: Option<JavaSignatureTag>,
244    pub(crate) value: JavaValueExpr,
245}
246
247/// #this.x #["Ljava/lang/String;"] = ...;
248pub(crate) struct JavaFieldWrite {
249    pub(crate) obj: Ident,
250    pub(crate) field: Ident,
251    /// only needed if the type can't be inferred.
252    pub(crate) signature: Option<JavaSignatureTag>,
253    pub(crate) value: JavaValueExpr,
254}
255
256/// #this.x #["J"]
257pub(crate) struct JavaFieldRead {
258    pub(crate) obj: Ident,
259    pub(crate) field: Ident,
260    /// only needed if the type can't be inferred.
261    pub(crate) signature: Option<JavaSignatureTag>,
262}
263
264/// #arr[...] #["Z"] = ...;
265pub(crate) struct JavaArrayWrite {
266    pub(crate) array: Ident,
267    pub(crate) index: JavaValueExpr,
268    /// only needed if the type can't be inferred.
269    pub(crate) signature: Option<JavaSignatureTag>,
270    pub(crate) value: JavaValueExpr,
271}
272
273/// #arr[...] #["I"]
274pub(crate) struct JavaArrayRead {
275    pub(crate) array: Ident,
276    pub(crate) index: JavaValueExpr,
277    /// only needed if the type can't be inferred.
278    pub(crate) signature: Option<JavaSignatureTag>,
279}
280
281/// #throw new Class(...);
282pub(crate) struct JavaThrowNew {
283    pub(crate) class: DottedName,
284    pub(crate) message: JavaValueExpr,
285}
286
287/// #throw ...;
288pub(crate) struct JavaThrowObj {
289    pub(crate) value: JavaValueExpr,
290}
291
292/// #new Class(...) #["IJJLcom/beatcraft/interop/Note;"]
293pub(crate) struct JavaNew {
294    pub(crate) class: DottedName,
295    pub(crate) args: Punctuated<JavaValueExpr, Token![,]>,
296    /// May be None if the ctor has no args or can all be inferred.
297    pub(crate) signature: Option<JavaSignatureTag>,
298}
299
300/// #alloc Class()
301pub(crate) struct JavaAlloc {
302    pub(crate) class: DottedName,
303}
304
305/*
306 * java_class! {
307 *     package com.beatcraft.interop;
308 *
309 *     import org.joml.Vector3f;
310 *
311 *     use crate::com.beatcraft.interop.Info;
312 *
313 *     class BeatcraftCore {
314 *         handle: long;
315 *         static thing: int;
316 *
317 *         static native fn func(x: float, y: float) -> BeatcraftCore {
318 *             let handle = ...
319 *             let this = #alloc BeatcraftCore();
320 *             #this.handle = handle;
321 *             this
322 *         }
323 *         native fn method(x: float, y: float) -> float {
324 *             let handle = #this.handle;
325 *         }
326 *         static fn thing(...) -> ...;
327 *         fn thing2(...) -> ...;
328 *     }
329 *
330 * }
331 *
332 */
333
334/// Allows writing a java class binding with more ergonomic syntax,
335/// and simplifies many requirements such as class paths, and jni plumbing
336///
337/// Example usage:
338/// ```rs
339/// java_class! {
340///     package com.example.project;
341///
342///     // You can import classes to avoid writting fully qualified
343///     // class names later on.
344///     import org.joml.Vector3f;
345///
346///     // You can also use classes defined elsewhere.
347///     // This allows for functions to return the wrapper struct,
348///     // instead of just JObject.
349///     // the java classpath is also required for the same reason
350///     // that imports are allowed.
351///     use crate::class::com.example.project.Class2;
352///
353///     class MyClass {
354///         // class fields can be specified to simplify later code.
355///         // note: this is currently experimental, and field types here
356///         // apply to any java field access from any class.
357///         static val1: Vector3f;
358///         handle: long;
359///         val2: int;
360///
361///         // prefixing `native` before a function means the function
362///         // implementation is here in rust.
363///         // prefixing `static` makes this a static method.
364///         // static native functions always have implicit access to
365///         // 2 extra variables:
366///         // env: &mut Env<'c>
367///         // class: &JClass<'c>
368///         static native fn new_1(a: float, b: float) -> MyClass {
369///             let handle = ...;
370///             let this = #alloc MyClass();
371///
372///             // the {...} around handle are needed since
373///             // this allows for more complex rust code to be used,
374///             // such as function calls.
375///             #this.handle = {handle};
376///             #this.val2 = {0};
377///
378///             this
379///         }
380///
381///         // non-static native functions have implicit access to:
382///         // env: &mut Env<'c>
383///         // this: &JObject<'c>
384///         native fn increment() {
385///             let mut val2 = #this.val2;
386///             val2 += 1;
387///             #this.val2 = {val2};
388///         }
389///
390///         // non-native functions allow calling java functions directly in rust.
391///         // note that overloaded functions are impossible to define more than 1 of.
392///         static fn do_java_stuff(i: int) -> String;
393///         fn do_stuff2() -> int;
394///     }
395/// }
396/// ```
397#[proc_macro]
398pub fn java_class(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
399    let class: JavaClassDef = parse_macro_input!(input);
400    let class = class.resolve();
401    quote! {
402        #class
403    }.into()
404}
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421