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}