yutani_codegen/
lib.rs

1mod proto;
2use std::{path::Path, io, string::FromUtf8Error};
3
4use heck::{ToPascalCase, ToSnakeCase, ToShoutySnakeCase};
5use proc_macro2::{TokenStream, Ident, Span};
6pub use proto::*;
7use quote::quote;
8
9pub type Result<T> = core::result::Result<T, Error>;
10#[derive(Debug)]
11pub enum Error {
12    Toml(toml::de::Error),
13    Io(io::Error),
14    Utf8(FromUtf8Error)
15}
16impl From<toml::de::Error> for Error {
17    fn from(error: toml::de::Error) -> Self {
18        Self::Toml(error)
19    }
20}
21impl From<io::Error> for Error {
22    fn from(error: io::Error) -> Self {
23        Self::Io(error)
24    }
25}
26impl From<FromUtf8Error> for Error {
27    fn from(error: FromUtf8Error) -> Self {
28        Self::Utf8(error)
29    }
30}
31
32pub fn protocol<P: AsRef<Path>>(path: P) -> Result<TokenStream> {
33    let protocol = proto::Protocol::load(path)?;
34    let header = format!("# {}", protocol.name);
35    let summary = protocol.summary.map(|summary| quote! {#![doc = #summary]});
36    let description = protocol.description.map(|description| quote! {#![doc = #description]});
37    let copyright = protocol.copyright.map(|copyright| quote! {
38        #![doc = "## Copyright"]
39        #![doc = #copyright]
40    });
41
42    let interfaces = protocol.interfaces.into_iter().map(|i| interface(i));
43
44    Ok(quote!{
45        #![doc = #header]
46        #summary
47        #![doc = ""]
48        #description
49        #copyright
50        
51        #(#interfaces)*
52    })
53}
54
55pub fn interface(interface: Interface) -> TokenStream {
56    let trait_ident = Ident::new_raw(&interface.name.to_pascal_case(), Span::call_site());
57    let mod_ident = Ident::new_raw(&interface.name.to_snake_case(), Span::call_site());
58    let name = interface.name;
59    let version = interface.version;
60    let version_doc = format!("`Version {}`", interface.version);
61    let summary = interface.summary.map(|summary| quote!{#[doc = #summary]});
62    let description = interface.description.map(|description| quote! {#[doc = #description]});
63
64    let enums = interface.enums.into_iter().map(|e| enumeration(e));
65    let requests = interface.requests.iter().map(|r| request(r));
66    let events = interface.events.iter().enumerate().map(|(opcode, e)| event(e, opcode.try_into().unwrap()));
67
68    let dispatch_requests = interface.requests.iter().enumerate().map(|(opcode, r)| {
69        let opcode: u16 = opcode.try_into().unwrap();
70        let ident = Ident::new_raw(&r.name.to_snake_case(), Span::call_site());
71        let stream = Ident::new("_stream", Span::call_site());
72
73        let define_args = r.args.iter().map(|a| {
74            let ident = Ident::new_raw(&a.name.to_snake_case(), Span::call_site());
75            let getter = a.getter(&stream);
76            quote!{let #ident = #getter;}
77        });
78        let args = r.args.iter().map(|a| {
79            let ident = Ident::new_raw(&a.name.to_snake_case(), Span::call_site());
80            quote!{#ident}
81        });
82        quote!{
83            #opcode => {
84                let #stream = _client.stream();
85                #(#define_args)*
86                Self::#ident(_this, _event_loop, _client #(, #args)*)
87            }
88        }
89    });
90
91    quote!{
92        #summary
93        #[doc = ""]
94        #[doc = #version_doc]
95        #[doc = ""]
96        #description
97        pub trait #trait_ident<T>: 'static + ::core::marker::Sized {
98            const INTERFACE: &'static ::core::primitive::str = #name;
99            const VERSION: ::core::primitive::u32 = #version;
100            #[doc(hidden)]
101            fn dispatch(_this: ::yutani::lease::Lease<dyn ::core::any::Any>, _event_loop: &mut ::yutani::wire::EventLoop<T>, _client: &mut ::yutani::server::Client<T>, _message: ::yutani::wire::Message) -> ::core::result::Result<(), ::yutani::wire::WlError<'static>> {
102                let _this: ::yutani::lease::Lease<Self> = _this.downcast().ok_or(::yutani::wire::WlError::INTERNAL)?;
103                match _message.opcode {
104                    #(#dispatch_requests,)*
105                    _ => ::core::result::Result::Err(::yutani::wire::WlError::INVALID_OPCODE)
106                }
107            }
108            #[doc = "Create a new object that can be tracked by `yutani`"]
109            fn into_object(self, id: ::yutani::Id) -> ::yutani::lease::Resident<Self, T, ::yutani::server::Client<T>> {
110                ::yutani::lease::Resident::new(id, Self::dispatch, Self::INTERFACE, Self::VERSION, self)
111            }
112            #[doc = "Create a new object that can be tracked by `yutani`, with a given version"]
113            fn into_versioned_object(self, id: ::yutani::Id, version: u32) -> ::core::result::Result<::yutani::lease::Resident<Self, T, ::yutani::server::Client<T>>, ::yutani::wire::WlError<'static>> {
114                if version > Self::VERSION {
115                    ::core::result::Result::Err(::yutani::wire::WlError::UNSUPPORTED_VERSION)
116                } else {
117                    ::core::result::Result::Ok(::yutani::lease::Resident::new(id, Self::dispatch, Self::INTERFACE, version, self))
118                }
119            }
120            #(#requests)*
121            #(#events)*
122        }
123        pub mod #mod_ident {
124            #(#enums)*
125        }
126    }
127}
128
129pub fn enumeration(enumeration: Enum) -> TokenStream {
130    let ident = Ident::new_raw(&enumeration.name.to_pascal_case(), Span::call_site());
131    let since = enumeration.since.map(|since| {
132        let since = format!("`Since version {}`", since);
133        quote!{
134            #[doc = ""]
135            #[doc = #since]
136        }
137    });
138    let summary = enumeration.summary.map(|summary| quote!{#[doc = #summary]});
139    let description = enumeration.description.map(|description| quote! {#[doc = #description]});
140
141    let entries = enumeration.entries.iter().map(|entry| {
142        let name = if entry.name.starts_with(char::is_numeric) {
143            format!("{}_{}", enumeration.name, entry.name).to_shouty_snake_case()
144        } else { entry.name.to_shouty_snake_case() };
145        let ident = Ident::new_raw(&name, Span::call_site());
146        let since = entry.since.map(|since| {
147            let since = format!("`Since version {}`", since);
148            quote!{
149                #[doc = ""]
150                #[doc = #since]
151            }
152        });
153        let summary = entry.summary.as_ref().map(|summary| quote!{#[doc = #summary]});
154        let description = entry.description.as_ref().map(|description| quote! {#[doc = #description]});
155        let value = entry.value;
156        quote!{
157            #summary
158            #since
159            #[doc = ""]
160            #description
161            pub const #ident: Self = Self(#value);
162        }
163    });
164    let entries_debug = enumeration.entries.iter().map(|entry| {
165        let name = if entry.name.starts_with(char::is_numeric) {
166            format!("{}_{}", enumeration.name, entry.name).to_shouty_snake_case()
167        } else { entry.name.to_shouty_snake_case() };
168        let value = entry.value;
169        quote!{#value => ::core::write!(f, "{}({})", #name, #value)}
170    });
171
172    quote!{
173        #summary
174        #since
175        #[doc = ""]
176        #description
177        #[repr(transparent)]
178        pub struct #ident(u32);
179        impl #ident {
180            #(#entries)*
181        }
182        impl ::core::convert::From<::core::primitive::u32> for #ident {
183            fn from(value: ::core::primitive::u32) -> Self {
184                Self(value)
185            }
186        }
187        impl ::core::convert::Into<::core::primitive::u32> for #ident {
188            fn into(self) -> ::core::primitive::u32 {
189                self.0
190            }
191        }
192        impl ::core::fmt::Debug for #ident {
193            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
194                match self.0 {
195                    #(#entries_debug,)*
196                    value => ::core::write!(f, "UNKNOWN({})", value)
197                }
198            }
199        }
200    }
201}
202
203pub fn request(request: &Request) -> TokenStream {
204    let ident = Ident::new_raw(&request.name.to_snake_case(), Span::call_site());
205    let since = request.since.map(|since| {
206        let since = format!("`Since version {}`", since);
207        quote!{
208            #[doc = ""]
209            #[doc = #since]
210        }
211    });
212    let summary = request.summary.as_ref().map(|summary| quote!{#[doc = #summary]});
213    let description = request.description.as_ref().map(|description| quote! {#[doc = #description]});
214
215    let args = request.args.iter().map(|a| {
216        let ident = Ident::new_raw(&a.name.to_snake_case(), Span::call_site());
217        let ty = a.ty();
218        quote!{
219            #ident: #ty
220        }
221    });
222    let arg_summaries: Vec<_> = request.args.iter().filter_map(|a| {
223        a.summary.as_ref().map(|summary| {
224            let summary = format!("\n`{}`: {}", a.name, summary);
225            quote!{#[doc = #summary]}
226        })
227    }).collect();
228    let arg_summaries_header = if arg_summaries.is_empty() {
229        None
230    } else {
231        Some(quote!{
232            #[doc = ""]
233            #[doc = "## Arguments"]
234        })
235    };
236
237    quote!{
238        #summary
239        #since
240        #[doc = ""]
241        #description
242        #arg_summaries_header
243        #(#arg_summaries)*
244        fn #ident(this: ::yutani::lease::Lease<Self>, event_loop: &mut ::yutani::wire::EventLoop<T>, client: &mut ::yutani::server::Client<T> #(, #args)*) -> ::core::result::Result<(), ::yutani::wire::WlError<'static>>;
245    }
246}
247
248pub fn event(event: &Event, opcode: u16) -> TokenStream {
249    let ident = Ident::new_raw(&event.name.to_snake_case(), Span::call_site());
250    let stream = Ident::new("_stream", Span::call_site());
251    let since = event.since.map(|since| {
252        let since = format!("`Since version {}`", since);
253        quote!{
254            #[doc = ""]
255            #[doc = #since]
256        }
257    });
258    let summary = event.summary.as_ref().map(|summary| quote!{#[doc = #summary]});
259    let description = event.description.as_ref().map(|description| quote! {#[doc = #description]});
260
261    let args = event.args.iter().map(|a| {
262        let ident = Ident::new_raw(&a.name.to_snake_case(), Span::call_site());
263        let ty = a.send_ty();
264        quote!{
265            #ident: #ty
266        }
267    });
268    let args_senders = event.args.iter().map(|a| a.sender(&stream));
269    let arg_summaries: Vec<_> = event.args.iter().filter_map(|a| {
270        a.summary.as_ref().map(|summary| {
271            let summary = format!("\n`{}`: {}", a.name, summary);
272            quote!{#[doc = #summary]}
273        })
274    }).collect();
275    let arg_summaries_header = if arg_summaries.is_empty() {
276        None
277    } else {
278        Some(quote!{
279            #[doc = ""]
280            #[doc = "## Arguments"]
281        })
282    };
283
284    quote!{
285        #summary
286        #since
287        #[doc = ""]
288        #description
289        #arg_summaries_header
290        #(#arg_summaries)*
291        fn #ident(this: &mut ::yutani::lease::Lease<Self>, client: &mut ::yutani::server::Client<T> #(, #args)*) -> ::core::result::Result<(), ::yutani::wire::WlError<'static>> {
292            let #stream = client.stream();
293            let _key = #stream.start_message(this.id(), #opcode);
294            #(#args_senders;)*
295            #stream.commit(_key)
296        }
297    }
298}