1use proc_macro2::TokenStream;
2
3use crate::protocol::{Interface, Message, Protocol, Type};
4
5use quote::{format_ident, quote};
6
7pub fn generate(protocol: &Protocol, with_c_interfaces: bool) -> TokenStream {
8 let interfaces: impl Iterator =
9 protocol.interfaces.iter().map(|iface: &Interface| generate_interface(interface:iface, with_c_interfaces));
10 if with_c_interfaces {
11 let prefix: TokenStream = super::c_interfaces::generate_interfaces_prefix(protocol);
12 quote! {
13 #prefix
14 #(#interfaces)*
15 }
16 } else {
17 interfaces.collect()
18 }
19}
20
21pub(crate) fn generate_interface(interface: &Interface, with_c: bool) -> TokenStream {
22 let const_name = format_ident!("{}_INTERFACE", interface.name.to_ascii_uppercase());
23 let iface_name = &interface.name;
24 let iface_version = interface.version;
25 let requests = build_messagedesc_list(&interface.requests);
26 let events = build_messagedesc_list(&interface.events);
27
28 let c_name = format_ident!("{}_interface", interface.name);
29
30 if with_c {
31 let c_iface = super::c_interfaces::generate_interface(interface);
32 quote! {
33 pub static #const_name: wayland_backend::protocol::Interface = wayland_backend::protocol::Interface {
34 name: #iface_name,
35 version: #iface_version,
36 requests: #requests,
37 events: #events,
38 c_ptr: Some(unsafe { & #c_name }),
39 };
40
41 #c_iface
42 }
43 } else {
44 quote! {
45 pub static #const_name: wayland_backend::protocol::Interface = wayland_backend::protocol::Interface {
46 name: #iface_name,
47 version: #iface_version,
48 requests: #requests,
49 events: #events,
50 c_ptr: None,
51 };
52 }
53 }
54}
55
56fn build_messagedesc_list(list: &[Message]) -> TokenStream {
57 let desc_list = list.iter().map(|message| {
58 let name = &message.name;
59 let since = message.since;
60 let is_destructor = message.typ == Some(Type::Destructor);
61 let signature = message.args.iter().map(|arg| {
62 if arg.typ == Type::NewId && arg.interface.is_none() {
63 // this is a special generic message, it expands to multiple arguments
64 quote! {
65 wayland_backend::protocol::ArgumentType::Str(wayland_backend::protocol::AllowNull::No),
66 wayland_backend::protocol::ArgumentType::Uint,
67 wayland_backend::protocol::ArgumentType::NewId
68 }
69 } else {
70 let typ = arg.typ.common_type();
71 if arg.typ.nullable() {
72 if arg.allow_null {
73 quote! { wayland_backend::protocol::ArgumentType::#typ(wayland_backend::protocol::AllowNull::Yes) }
74 } else {
75 quote! { wayland_backend::protocol::ArgumentType::#typ(wayland_backend::protocol::AllowNull::No) }
76 }
77 } else {
78 quote! { wayland_backend::protocol::ArgumentType::#typ }
79 }
80 }
81 });
82 let child_interface = match message
83 .args
84 .iter()
85 .find(|arg| arg.typ == Type::NewId)
86 .and_then(|arg| arg.interface.as_ref())
87 {
88 Some(name) => {
89 let target_iface = format_ident!("{}_INTERFACE", name.to_ascii_uppercase());
90 quote! { Some(&#target_iface) }
91 }
92 None => quote! { None },
93 };
94 let arg_interfaces = message.args.iter().filter(|arg| arg.typ == Type::Object).map(|arg| {
95 match arg.interface {
96 Some(ref name) => {
97 let target_iface = format_ident!("{}_INTERFACE", name.to_ascii_uppercase());
98 quote! { &#target_iface }
99 }
100 None => {
101 quote! { &wayland_backend::protocol::ANONYMOUS_INTERFACE }
102 }
103 }
104 });
105 quote! {
106 wayland_backend::protocol::MessageDesc {
107 name: #name,
108 signature: &[ #(#signature),* ],
109 since: #since,
110 is_destructor: #is_destructor,
111 child_interface: #child_interface,
112 arg_interfaces: &[ #(#arg_interfaces),* ],
113 }
114 }
115 });
116
117 quote!(
118 &[ #(#desc_list),* ]
119 )
120}
121
122#[cfg(test)]
123mod tests {
124 #[test]
125 fn interface_gen() {
126 let protocol_file =
127 std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
128 let protocol_parsed = crate::parse::parse(protocol_file);
129 let generated: String = super::generate(&protocol_parsed, true).to_string();
130 let generated = crate::format_rust_code(&generated);
131
132 let reference =
133 std::fs::read_to_string("./tests/scanner_assets/test-interfaces.rs").unwrap();
134 let reference = crate::format_rust_code(&reference);
135
136 if reference != generated {
137 let diff = similar::TextDiff::from_lines(&reference, &generated);
138 print!("{}", diff.unified_diff().context_radius(10).header("reference", "generated"));
139 panic!("Generated does not match reference!")
140 }
141 }
142}
143