blob: d9d48d0b499a662e26cf9235670b76aecab42175 [file] [log] [blame] [edit]
#[allow(unused_imports)]
#[allow(deprecated)]
use std::ascii::AsciiExt;
use std::iter;
use std::str;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::{quote, ToTokens};
use crate::protocol::*;
use crate::util::*;
use crate::Side;
pub(crate) fn to_doc_attr(text: &str) -> TokenStream {
let text = text.lines().map(str::trim).collect::<Vec<_>>().join("\n");
let text = text.trim();
quote!(#[doc = #text])
}
pub(crate) fn description_to_doc_attr(&(ref short, ref long): &(String, String)) -> TokenStream {
to_doc_attr(&format!("{}\n\n{}", short, long))
}
impl ToTokens for Enum {
fn to_tokens(&self, tokens: &mut TokenStream) {
let enum_decl;
let enum_impl;
let doc_attr = self.description.as_ref().map(description_to_doc_attr);
let ident = Ident::new(&snake_to_camel(&self.name), Span::call_site());
if self.bitfield {
let entries = self.entries.iter().map(|entry| {
let doc_attr = entry
.description
.as_ref()
.map(description_to_doc_attr)
.or_else(|| entry.summary.as_ref().map(|s| to_doc_attr(s)));
let prefix = if entry.name.chars().next().unwrap().is_numeric() {
"_"
} else {
""
};
let ident = Ident::new(
&format!("{}{}", prefix, snake_to_camel(&entry.name)),
Span::call_site(),
);
let value = Literal::u32_unsuffixed(entry.value);
quote! {
#doc_attr
const #ident = #value;
}
});
enum_decl = quote! {
bitflags! {
#doc_attr
pub struct #ident: u32 {
#(#entries)*
}
}
};
enum_impl = quote! {
impl #ident {
pub fn from_raw(n: u32) -> Option<#ident> {
Some(#ident::from_bits_truncate(n))
}
pub fn to_raw(&self) -> u32 {
self.bits()
}
}
};
} else {
let variants = self.entries.iter().map(|entry| {
let doc_attr = entry
.description
.as_ref()
.map(description_to_doc_attr)
.or_else(|| entry.summary.as_ref().map(|s| to_doc_attr(s)));
let prefix = if entry.name.chars().next().unwrap().is_numeric() {
"_"
} else {
""
};
let variant = Ident::new(
&format!("{}{}", prefix, snake_to_camel(&entry.name)),
Span::call_site(),
);
let value = Literal::u32_unsuffixed(entry.value);
quote! {
#doc_attr
#variant = #value
}
});
enum_decl = quote! {
#doc_attr
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum #ident {
#(#variants,)*
}
};
let match_arms = self.entries.iter().map(|entry| {
let value = Literal::u32_unsuffixed(entry.value);
let prefix = if entry.name.chars().next().unwrap().is_numeric() {
"_"
} else {
""
};
let variant = Ident::new(
&format!("{}{}", prefix, snake_to_camel(&entry.name)),
Span::call_site(),
);
quote! {
#value => Some(#ident::#variant)
}
});
enum_impl = quote! {
impl #ident {
pub fn from_raw(n: u32) -> Option<#ident> {
match n {
#(#match_arms,)*
_ => Option::None
}
}
pub fn to_raw(&self) -> u32 {
*self as u32
}
}
};
}
enum_decl.to_tokens(tokens);
enum_impl.to_tokens(tokens);
}
}
pub(crate) fn gen_since_constants(requests: &[Message], events: &[Message]) -> TokenStream {
let req_constants = requests.iter().map(|msg| {
let cstname = Ident::new(
&format!("REQ_{}_SINCE", msg.name.to_ascii_uppercase()),
Span::call_site(),
);
let since = msg.since;
quote! {
/// The minimal object version supporting this request
pub const #cstname: u32 = #since;
}
});
let evt_constants = events.iter().map(|msg| {
let cstname = Ident::new(
&format!("EVT_{}_SINCE", msg.name.to_ascii_uppercase()),
Span::call_site(),
);
let since = msg.since;
quote! {
/// The minimal object version supporting this event
pub const #cstname: u32 = #since;
}
});
quote! {
#(#req_constants)*
#(#evt_constants)*
}
}
pub(crate) fn gen_messagegroup(
name: &Ident,
side: Side,
receiver: bool,
messages: &[Message],
addon: Option<TokenStream>,
) -> TokenStream {
let variants = messages.iter().map(|msg| {
let mut docs = String::new();
if let Some((ref short, ref long)) = msg.description {
docs += &format!("{}\n\n{}\n", short, long.trim());
}
if let Some(Type::Destructor) = msg.typ {
docs += &format!(
"\nThis is a destructor, once {} this object cannot be used any longer.",
if receiver { "received" } else { "sent" },
);
}
if msg.since > 1 {
docs += &format!(
"\nOnly available since version {} of the interface",
msg.since
);
}
let doc_attr = to_doc_attr(&docs);
let msg_name = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let msg_variant_decl = if msg.args.is_empty() {
msg_name.into_token_stream()
} else {
let fields = msg.args.iter().flat_map(|arg| {
let field_name = Ident::new(
&format!(
"{}{}",
if is_keyword(&arg.name) { "_" } else { "" },
arg.name
),
Span::call_site(),
);
let field_type_inner = if let Some(ref enu) = arg.enum_ {
dotted_to_relname(enu)
} else {
match arg.typ {
Type::Uint => quote!(u32),
Type::Int => quote!(i32),
Type::Fixed => quote!(f64),
Type::String => quote!(String),
Type::Array => quote!(Vec<u8>),
Type::Fd => quote!(::std::os::unix::io::RawFd),
Type::Object => {
if let Some(ref iface) = arg.interface {
let iface_mod = Ident::new(&iface, Span::call_site());
let iface_type =
Ident::new(&snake_to_camel(iface), Span::call_site());
quote!(super::#iface_mod::#iface_type)
} else {
quote!(AnonymousObject)
}
}
Type::NewId if !receiver && side == Side::Client => {
// Client-side sending does not have a pre-existing object
// so skip serializing it
if arg.interface.is_some() {
return None;
} else {
quote!((String, u32))
}
}
Type::NewId => {
let object_name = if side == Side::Server && !receiver {
Ident::new("Resource", Span::call_site())
} else {
Ident::new("Main", Span::call_site())
};
if let Some(ref iface) = arg.interface {
let iface_mod = Ident::new(&iface, Span::call_site());
let iface_type =
Ident::new(&snake_to_camel(iface), Span::call_site());
quote!(#object_name<super::#iface_mod::#iface_type>)
} else {
// bind-like function
quote!((String, u32, AnonymousObject))
}
}
Type::Destructor => panic!("An argument cannot have type \"destructor\"."),
}
};
let field_type = if arg.allow_null {
quote!(Option<#field_type_inner>)
} else {
field_type_inner.into_token_stream()
};
Some(quote! {
#field_name: #field_type
})
});
quote! {
#msg_name {
#(#fields,)*
}
}
};
quote! {
#doc_attr
#msg_variant_decl
}
});
let message_array_values = messages.iter().map(|msg| {
let name_value = &msg.name;
let since_value = Literal::u32_unsuffixed(msg.since);
let signature_values = msg.args.iter().map(|arg| {
let common_type = arg.typ.common_type();
quote!(super::ArgumentType::#common_type)
});
let is_destructor = msg.typ == Some(Type::Destructor);
quote! {
super::MessageDesc {
name: #name_value,
since: #since_value,
signature: &[
#(#signature_values,)*
],
destructor: #is_destructor,
}
}
});
let map_type = if side == Side::Client {
quote!(ProxyMap)
} else {
quote!(ResourceMap)
};
// Can't be a closure because closures are never Copy / Clone in rustc < 1.26.0, and we supports 1.21.0
fn map_fn((ref msg, ref name): (&Message, &Ident)) -> TokenStream {
let msg_type = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let msg_type_qualified = quote!(#name::#msg_type);
if msg.args.is_empty() {
msg_type_qualified
} else {
quote!(#msg_type_qualified { .. })
}
}
let message_match_patterns = messages.iter().zip(iter::repeat(name)).map(map_fn);
let mut is_destructor_match_arms = messages
.iter()
.zip(message_match_patterns.clone())
.filter(|&(msg, _)| msg.typ == Some(Type::Destructor))
.map(|(_, pattern)| quote!(#pattern => true))
.collect::<Vec<_>>();
if messages.len() > is_destructor_match_arms.len() {
is_destructor_match_arms.push(quote!(_ => false));
}
let opcode_match_arms = message_match_patterns
.clone()
.enumerate()
.map(|(opcode, pattern)| {
let value = Literal::u16_unsuffixed(opcode as u16);
quote!(#pattern => #value)
});
let since_match_arms = messages
.iter()
.zip(message_match_patterns)
.map(|(msg, pattern)| {
let since = Literal::u32_unsuffixed(msg.since as u32);
quote!(#pattern => #since)
});
let child_match_arms = messages
.iter()
.enumerate()
.filter_map(|(opcode, msg)| {
let mut it = msg.args.iter().filter_map(|a| {
if a.typ == Type::NewId {
a.interface.as_ref()
} else {
None
}
});
it.next().map(|new_iface| {
assert!(
it.next().is_none(),
"Got a message with more than one new_id in {}.{}",
name,
msg.name
);
let pattern = Literal::u16_unsuffixed(opcode as u16);
let new_iface_mod = Ident::new(new_iface, Span::call_site());
let new_iface_type = Ident::new(&snake_to_camel(new_iface), Span::call_site());
quote! {
#pattern => Some(Object::from_interface::<super::#new_iface_mod::#new_iface_type>(
version,
meta.child(),
))
}
})
})
.chain(iter::once(quote!(_ => None)));
let from_raw_body = if receiver {
let match_arms = messages
.iter()
.enumerate()
.map(|(opcode, msg)| {
let pattern = Literal::u16_unsuffixed(opcode as u16);
let msg_type = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let msg_type_qualified = quote!(#name::#msg_type);
let block = if msg.args.is_empty() {
quote!(Ok(#msg_type_qualified))
} else {
let fields = msg.args.iter().map(|arg| {
let field_name = Ident::new(
&format!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name),
Span::call_site(),
);
let some_code_path = match arg.typ {
Type::Int => {
if let Some(ref enu) = arg.enum_ {
let enum_ident = dotted_to_relname(enu);
quote!(#enum_ident::from_raw(val as u32).ok_or(())?)
} else {
quote!(val)
}
}
Type::Uint => {
if let Some(ref enu) = arg.enum_ {
let enum_ident = dotted_to_relname(enu);
quote!(#enum_ident::from_raw(val).ok_or(())?)
} else {
quote!(val)
}
}
Type::Fixed => quote!((val as f64) / 256.),
Type::Array => {
if arg.allow_null {
quote!(if val.len() == 0 { None } else { Some(*val) })
} else {
quote!(*val)
}
}
Type::String => {
let string_conversion = quote! {
let s = String::from_utf8(val.into_bytes())
.unwrap_or_else(|e| String::from_utf8_lossy(&e.into_bytes()).into());
};
if arg.allow_null {
quote! {
#string_conversion
if s.len() == 0 { None } else { Some(s) }
}
} else {
quote! {
#string_conversion
s
}
}
}
Type::Fd => quote!(val),
Type::Object => {
let map_lookup = if side == Side::Client {
quote!(map.get_or_dead(val).into())
} else {
quote!(map.get(val).ok_or(())?.into())
};
if arg.allow_null {
quote!(if val == 0 { None } else { Some(#map_lookup) })
} else {
map_lookup
}
}
Type::NewId => {
let map_lookup = quote!(map.get_new(val).ok_or(())?);
if arg.interface.is_none() {
quote! {
{
let resource: Resource<AnonymousObject> = #map_lookup.into();
(String::new(), 0, resource.into())
}
}
} else if arg.allow_null {
quote!(if val == 0 { None } else { Some(#map_lookup) })
} else {
map_lookup
}
}
Type::Destructor => panic!("An argument cannot have type destructor!"),
};
let common_type = arg.typ.common_type();
quote! {
#field_name: {
if let Some(Argument::#common_type(val)) = args.next() {
#some_code_path
} else {
return Err(());
}
}
}
});
quote! {
{
let mut args = msg.args.into_iter();
Ok(#msg_type_qualified {
#(#fields,)*
})
}
}
};
quote!(#pattern => #block)
})
.chain(iter::once(quote!(_ => Err(()))));
quote! {
match msg.opcode {
#(#match_arms,)*
}
}
} else {
let panic_message = format!("{}::from_raw can not be used {:?}-side.", name, side);
quote!(panic!(#panic_message))
};
let into_raw_body = if receiver {
let panic_message = format!("{}::into_raw can not be used {:?}-side.", name, side);
quote!(panic!(#panic_message))
} else {
let match_arms = messages.iter().enumerate().map(|(opcode, msg)| {
let msg_type = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let msg_type_qualified = quote!(#name::#msg_type);
let pattern = if msg.args.is_empty() {
msg_type_qualified
} else {
let fields = msg.args.iter().flat_map(|arg| {
// Client-side newid request do not contain a placeholder
if side == Side::Client && arg.typ == Type::NewId && arg.interface.is_some() {
None
} else {
Some(Ident::new(
&format!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name),
Span::call_site(),
))
}
});
quote!(#msg_type_qualified { #(#fields),* })
};
let opcode_value = Literal::u16_unsuffixed(opcode as u16);
let args_values = msg.args.iter().map(|arg| {
let arg_ident = Ident::new(
&format!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name),
Span::call_site(),
);
match arg.typ {
Type::Int => {
if arg.enum_.is_some() {
quote!(Argument::Int(#arg_ident.to_raw() as i32))
} else {
quote!(Argument::Int(#arg_ident))
}
}
Type::Uint => {
if arg.enum_.is_some() {
quote!(Argument::Uint(#arg_ident.to_raw()))
} else {
quote!(Argument::Uint(#arg_ident))
}
}
Type::Fixed => quote!(Argument::Fixed((#arg_ident * 256.) as i32)),
Type::String => {
if arg.allow_null {
quote! {
Argument::Str(Box::new(unsafe {
::std::ffi::CString::from_vec_unchecked(
#arg_ident.map(Into::into).unwrap_or_else(Vec::new),
)
}))
}
} else {
quote! {
Argument::Str(Box::new(unsafe {
::std::ffi::CString::from_vec_unchecked(#arg_ident.into())
}))
}
}
}
Type::Array => {
if arg.allow_null {
quote!(Argument::Array(Box::new(#arg_ident.unwrap_or_else(Vec::new))))
} else {
quote!(Argument::Array(Box::new(#arg_ident)))
}
}
Type::Fd => quote!(Argument::Fd(#arg_ident)),
Type::NewId => {
if arg.interface.is_some() {
let id = if side == Side::Client {
quote!(0)
} else {
quote!(#arg_ident.id())
};
quote!(Argument::NewId(#id))
} else {
let id = if side == Side::Client {
quote!(0)
} else {
quote!(#arg_ident.2.id())
};
quote! {
Argument::Str(Box::new(unsafe {
::std::ffi::CString::from_vec_unchecked(#arg_ident.0.into())
})),
Argument::Uint(#arg_ident.1),
Argument::NewId(#id)
}
}
}
Type::Object => {
if arg.allow_null {
quote!(Argument::Object(#arg_ident.map(|o| o.as_ref().id()).unwrap_or(0)))
} else {
quote!(Argument::Object(#arg_ident.as_ref().id()))
}
}
Type::Destructor => panic!("An argument cannot have type Destructor"),
}
});
quote!(#pattern => Message {
sender_id: sender_id,
opcode: #opcode_value,
args: smallvec![
#(#args_values,)*
],
})
});
quote! {
match self {
#(#match_arms,)*
}
}
};
quote! {
#[derive(Debug)]
#[non_exhaustive]
pub enum #name {
#(#variants,)*
}
impl super::MessageGroup for #name {
const MESSAGES: &'static [super::MessageDesc] = &[
#(#message_array_values,)*
];
type Map = super::#map_type;
fn is_destructor(&self) -> bool {
match *self {
#(#is_destructor_match_arms,)*
}
}
fn opcode(&self) -> u16 {
match *self {
#(#opcode_match_arms,)*
}
}
fn since(&self) -> u32 {
match *self {
#(#since_match_arms,)*
}
}
fn child<Meta: ObjectMetadata>(opcode: u16, version: u32, meta: &Meta) -> Option<Object<Meta>> {
match opcode {
#(#child_match_arms,)*
}
}
fn from_raw(msg: Message, map: &mut Self::Map) -> Result<Self, ()> {
#from_raw_body
}
fn into_raw(self, sender_id: u32) -> Message {
#into_raw_body
}
#addon
}
}
}
pub(crate) fn gen_interface(
name: &Ident,
low_name: &str,
version: u32,
addon: Option<TokenStream>,
side: Side,
) -> TokenStream {
let object_type = side.object_name();
let version_lit = Literal::u32_unsuffixed(version);
quote! {
#[derive(Clone, Eq, PartialEq)]
pub struct #name(#object_type<#name>);
impl AsRef<#object_type<#name>> for #name {
#[inline]
fn as_ref(&self) -> &#object_type<Self> {
&self.0
}
}
impl From<#object_type<#name>> for #name {
#[inline]
fn from(value: #object_type<Self>) -> Self {
#name(value)
}
}
impl From<#name> for #object_type<#name> {
#[inline]
fn from(value: #name) -> Self {
value.0
}
}
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{:?}", self.0))
}
}
impl Interface for #name {
type Request = Request;
type Event = Event;
const NAME: &'static str = #low_name;
const VERSION: u32 = #version_lit;
#addon
}
}
}
pub fn method_prototype<'a>(
iname: &Ident,
msg: &'a Message,
side: Side,
) -> (TokenStream, Option<&'a Arg>) {
let mut it = msg.args.iter().filter(|arg| arg.typ == Type::NewId);
let mut newid = it.next();
assert!(
newid.is_none() || it.next().is_none(),
"Request {}.{} returns more than one new_id",
iname,
msg.name
);
// Serverside we don't deal with NewId arguments and treat them as objects.
if side == Side::Server {
newid = None;
}
let fn_name = Ident::new(
&format!(
"{}{}",
if is_keyword(&msg.name) { "_" } else { "" },
msg.name
),
Span::call_site(),
);
let mut args = Vec::new();
let generics = if let Some(arg) = newid {
if arg.interface.is_none() {
args.push(quote!(version: u32));
Some(quote!(T: Interface + From<Proxy<T>> + AsRef<Proxy<T>>))
} else {
None
}
} else {
None
}
.into_iter();
args.extend(msg.args.iter().filter_map(|arg| {
let arg_type_inner = if let Some(ref name) = arg.enum_ {
dotted_to_relname(name)
} else {
let mut typ = arg.typ;
if typ == Type::NewId && side == Side::Server {
typ = Type::Object;
}
match typ {
Type::Object => arg
.interface
.as_ref()
.map(|iface| {
let iface_mod = Ident::new(iface, Span::call_site());
let iface_type = Ident::new(&snake_to_camel(iface), Span::call_site());
quote!(&super::#iface_mod::#iface_type)
})
.unwrap_or(quote!(&super::AnonymousObject)),
Type::NewId => {
// client-side, the return-type handles that
return None;
}
_ => arg.typ.rust_type(),
}
};
let arg_name = Ident::new(
&format!(
"{}{}",
if is_keyword(&arg.name) { "_" } else { "" },
arg.name
),
Span::call_site(),
);
let arg_type = if arg.allow_null {
quote!(Option<#arg_type_inner>)
} else {
arg_type_inner
};
Some(quote!(#arg_name: #arg_type))
}));
let return_type = if let Some(arg) = newid {
match arg.interface {
Some(ref iface) => {
let iface_mod = Ident::new(&iface, Span::call_site());
let iface_type = Ident::new(&snake_to_camel(&iface), Span::call_site());
quote!(Main<super::#iface_mod::#iface_type>)
}
None => quote!(Main<T>),
}
} else {
quote!(())
};
let prototype = quote! {
pub fn #fn_name#(<#generics>)*(&self, #(#args),*) -> #return_type
};
(prototype, newid)
}
pub(crate) fn gen_object_methods(name: &Ident, messages: &[Message], side: Side) -> TokenStream {
let outgoing_message_type = Ident::new(
match side {
Side::Client => "Request",
Side::Server => "Event",
},
Span::call_site(),
);
let method_impls = messages.iter().map(|msg| {
let mut docs = String::new();
if let Some((ref short, ref long)) = msg.description {
docs += &format!("{}\n\n{}\n", short, long);
}
if let Some(Type::Destructor) = msg.typ {
docs += "\nThis is a destructor, you cannot send requests to this object any longer once this method is called.";
}
if msg.since > 1 {
docs += &format!("\nOnly available since version {} of the interface.", msg.since);
}
let doc_attr = to_doc_attr(&docs);
let msg_name = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let (proto, return_type) = method_prototype(name, &msg, side);
let msg_init = if msg.args.is_empty() {
TokenStream::new()
} else {
let args = msg.args.iter().flat_map(|arg| {
let arg_name = Ident::new(
&format!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name),
Span::call_site(),
);
let arg_value = match (arg.typ, side) {
(Type::NewId, Side::Client) => {
if arg.interface.is_some() {
return None;
} else {
quote!((T::NAME.into(), version))
}
},
(Type::NewId, Side::Server) => {
if arg.allow_null {
quote!(#arg_name.map(|o| o.as_ref().clone()))
} else {
quote!(#arg_name.as_ref().clone())
}
}
(Type::Object, _) => {
if arg.allow_null {
quote!(#arg_name.map(|o| o.clone()))
} else {
quote!(#arg_name.clone())
}
}
_ => quote!(#arg_name),
};
Some(quote!(#arg_name: #arg_value))
});
quote!({ #(#args),* })
};
let send_stmt = match return_type {
Some(ret_type) if ret_type.interface.is_none() => {
quote!(self.0.send(msg, Some(version)).unwrap())
}
Some(_) => quote!(self.0.send(msg, None).unwrap()),
None => if side == Side::Client {
quote! { self.0.send::<AnonymousObject>(msg, None); }
} else {
quote! { self.0.send(msg); }
}
};
quote! {
#doc_attr
#proto {
let msg = #outgoing_message_type::#msg_name #msg_init;
#send_stmt
}
}
});
quote! {
impl #name {
#(#method_impls)*
}
}
}