1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse::{Parse, ParseStream};
4use syn::punctuated::Punctuated;
5use syn::{
6 bracketed, parse_macro_input,
7 token::{self, Comma},
8 Attribute, FnArg, Ident, LitStr, Pat, Path, Result, ReturnType, Signature, Token,
9};
10
11mod kw {
12 syn::custom_keyword!(docs);
13}
14
15const OC2_DOC_BASE: &str =
16 "https://github.com/fnuecke/oc2/blob/1.18-forge/src/main/resources/assets/oc2/doc/en_us/";
17
18struct FnDef(Signature, Vec<Attribute>);
19impl Parse for FnDef {
20 fn parse(input: ParseStream) -> Result<Self> {
21 let attrs = input.call(Attribute::parse_outer)?;
22 let signature = input.parse::<Signature>()?;
23 input.parse::<Token![;]>()?;
24 Ok(FnDef(signature, attrs))
25 }
26}
27
28struct OC2RpcDef {
29 oc_method_name: LitStr,
30 doc_path: Option<LitStr>,
31}
32
33impl Parse for OC2RpcDef {
34 fn parse(input: ParseStream) -> Result<Self> {
35 Ok(OC2RpcDef {
36 oc_method_name: input.parse::<LitStr>()?,
37 doc_path: if input.peek(Token![,]) {
38 input.parse::<Token![,]>()?;
39 input.parse::<kw::docs>()?;
40 input.parse::<Token![=]>()?;
41 Some(input.parse::<LitStr>()?)
42 } else {
43 None
44 },
45 })
46 }
47}
48
49#[proc_macro_attribute]
50pub fn rpc(attr: TokenStream, input: TokenStream) -> TokenStream {
51 let OC2RpcDef {
52 oc_method_name,
53 doc_path,
54 } = parse_macro_input!(attr as OC2RpcDef);
55 let FnDef(
56 Signature {
57 ident,
58 generics,
59 inputs,
60 output,
61 ..
62 },
63 attrs,
64 ) = parse_macro_input!(input as FnDef);
65 let where_clause = &generics.where_clause;
66
67 let ret_type = match output {
68 ReturnType::Default => quote! { Option<()> },
69 ReturnType::Type(_, t) => quote! { #t },
70 };
71
72 let doc_path = doc_path.map(|path| {
73 let doc_url = format!("[OC2 Docs]({}/{}):", OC2_DOC_BASE, path.value());
74 quote! {
75 #[doc = #doc_url]
76 }
77 });
78
79 let arg_idents: Vec<Box<Pat>> = inputs
80 .iter()
81 .filter_map(|v| {
82 if let FnArg::Typed(t) = v {
83 Some(t.pat.clone())
84 } else {
85 None
86 }
87 })
88 .collect();
89
90 let tokens = if arg_idents.is_empty() {
91 let mut call_bytes = (String::from("\0")
92 + &format!(
93 r#"{{"type":"invoke","data":{{"name":"{}","parameters": [],"deviceId":""#,
94 oc_method_name.value()
95 ))
96 .into_bytes();
97 let call_start_len = call_bytes.len();
98 let call_id_end_len = call_start_len + 36;
99 call_bytes.extend(std::iter::repeat(0).take(36));
100 call_bytes.extend(b"\"}}\0");
101 let call_full_len = call_bytes.len();
102
103 let call_iter = call_bytes.into_iter();
104
105 quote! {
106 #doc_path
107 #(#attrs)*
108 fn #ident #generics (&self, bus: &mut crate::DeviceBus) -> std::io::Result<#ret_type> #where_clause {
109 let mut call_bytes: [u8; #call_full_len ] = [ #(#call_iter),* ];
110 call_bytes[ #call_start_len .. #call_id_end_len].copy_from_slice(self.id().as_bytes());
111
112 let response: crate::Response<#ret_type> = bus.call_preserialized(&call_bytes)?;
113 Ok(response.data)
114 }
115 }
116 } else {
117 let arg_defs = inputs.into_iter();
118
119 quote! {
120 #doc_path
121 #(#attrs)*
122 fn #ident #generics (&self, bus: &mut crate::DeviceBus, #(#arg_defs),*) -> std::io::Result<#ret_type> #where_clause {
123 let response: crate::Response<#ret_type> = bus.call(&crate::Call::invoke(self.id(), #oc_method_name, &[#(&#arg_idents),*]))?;
124 Ok(response.data)
125 }
126 }
127 };
128
129 TokenStream::from(tokens)
130}
131
132struct DeviceData {
133 rust_name: Ident,
134 oc2_identity: LitStr,
135 docs: LitStr,
136 _bracket_token: token::Bracket,
137 capabilities: Punctuated<Path, Comma>,
138}
139
140impl Parse for DeviceData {
141 fn parse(input: ParseStream) -> Result<Self> {
142 let rust_name = input.parse::<Ident>()?;
143 input.parse::<Comma>()?;
144 let oc2_identity = input.parse::<LitStr>()?;
145 input.parse::<Comma>()?;
146 let docs = input.parse::<LitStr>()?;
147 input.parse::<Comma>()?;
148
149 let content;
150 Ok(DeviceData {
151 rust_name,
152 oc2_identity,
153 docs,
154 _bracket_token: bracketed!(content in input),
155 capabilities: content.parse_terminated(Path::parse)?,
156 })
157 }
158}
159
160#[proc_macro]
161pub fn define_device(tokens: TokenStream) -> TokenStream {
162 let DeviceData {
163 rust_name,
164 docs,
165 oc2_identity,
166 capabilities,
167 ..
168 } = parse_macro_input!(tokens as DeviceData);
169
170 let capabilities = capabilities.into_iter();
171
172 let tokens = quote! {
173 #[doc = #docs ]
174 pub struct #rust_name(pub String);
175
176 impl RPCDevice for #rust_name {
177 fn id(&self) -> &str {
178 &self.0
179 }
180
181 fn from_id(id: String) -> Self {
182 #rust_name(id)
183 }
184 }
185
186 impl IdentifiedDevice for #rust_name {
187 const IDENTITY: &'static str = #oc2_identity;
188 }
189
190 #(impl #capabilities for #rust_name {})*
191 };
192
193 TokenStream::from(tokens)
194}