Replace simple_event_shunt macro with proc macro

This macro is pretty complicated and I needed to add the ability to
clean keywords, so it makes more sense to have this be a proc macro.
This commit is contained in:
Shawn Wallace 2024-10-15 02:17:20 -04:00
parent b988762955
commit 73ca9c91f1
6 changed files with 161 additions and 77 deletions

17
Cargo.lock generated
View file

@ -305,6 +305,14 @@ version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "macros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.2" version = "2.7.2"
@ -403,9 +411,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.36" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -516,9 +524,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.66" version = "2.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -859,6 +867,7 @@ dependencies = [
"env_logger 0.11.3", "env_logger 0.11.3",
"libc", "libc",
"log", "log",
"macros",
"paste", "paste",
"pretty_env_logger", "pretty_env_logger",
"rustix", "rustix",

View file

@ -1,4 +1,5 @@
[workspace] [workspace]
members = ["macros"]
[workspace.dependencies] [workspace.dependencies]
wayland-client = "0.31.2" wayland-client = "0.31.2"
wayland-protocols = "0.31.2" wayland-protocols = "0.31.2"
@ -35,6 +36,7 @@ xcb-util-cursor = "0.3.2"
smithay-client-toolkit = { version = "0.19.1", default-features = false } smithay-client-toolkit = { version = "0.19.1", default-features = false }
sd-notify = { version = "0.4.2", optional = true } sd-notify = { version = "0.4.2", optional = true }
macros = { version = "0.1.0", path = "macros" }
[features] [features]
default = [] default = []

11
macros/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
quote = "1.0.37"
syn = "2.0.79"

133
macros/src/lib.rs Normal file
View file

@ -0,0 +1,133 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
braced, bracketed, parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, Token,
};
enum FieldOrClosure {
Field(syn::Ident),
Closure(syn::Ident, syn::Expr),
}
impl Parse for FieldOrClosure {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse().map(|ident| Self::Field(ident)).or_else(|_| {
input.parse().map(|mut closure: syn::ExprClosure| {
assert_eq!(closure.inputs.len(), 1);
let syn::Pat::Ident(arg) = closure.inputs.pop().unwrap().into_value() else {
panic!("expected ident for closure argument");
};
Self::Closure(arg.ident, *closure.body)
})
})
}
}
struct EventVariant {
name: syn::Ident,
fields: Option<Punctuated<FieldOrClosure, Token![,]>>,
}
impl Parse for EventVariant {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
let fields = if input.peek(syn::token::Brace) {
let f;
braced!(f in input);
let f = Punctuated::parse_terminated(&f)?;
Some(f)
} else {
None
};
Ok(Self { name, fields })
}
}
struct Input {
object: syn::Expr,
event_object: syn::Ident,
event_type: syn::Type,
events: Punctuated<EventVariant, Token![,]>,
}
impl Parse for Input {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let object = input.parse()?;
input.parse::<Token![,]>()?;
let event_object = input.parse()?;
input.parse::<Token![:]>()?;
let event_type = input.parse()?;
input.parse::<Token![=>]>()?;
let events;
bracketed!(events in input);
let events = Punctuated::parse_terminated(&events)?;
Ok(Self {
object,
event_object,
event_type,
events,
})
}
}
#[proc_macro]
pub fn simple_event_shunt(tokens: TokenStream) -> TokenStream {
let Input {
object,
event_object,
event_type,
events,
} = parse_macro_input!(tokens as Input);
let match_arms = events.into_iter().map(|e| {
let mut field_names = Punctuated::<_, Token![,]>::new();
let mut fn_args = Punctuated::<syn::Expr, Token![,]>::new();
if let Some(fields) = e.fields {
for field in fields {
match field {
FieldOrClosure::Field(name) => {
fn_args.push(parse_quote! { #name });
field_names.push(name);
}
FieldOrClosure::Closure(name, expr) => {
field_names.push(name);
fn_args.push(expr);
}
}
}
}
let name = e.name;
let fn_name = String::from_utf8(
name.to_string()
.bytes()
.enumerate()
.flat_map(|(idx, c)| {
if idx != 0 && c.is_ascii_uppercase() {
vec![b'_', c.to_ascii_lowercase()]
} else {
vec![c.to_ascii_lowercase()]
}
})
.collect::<Vec<_>>(),
)
.unwrap();
let keyword_pfx = if fn_name == "type" { "_" } else { "" };
let fn_name = format_ident!("{keyword_pfx}{fn_name}");
quote! {
#name { #field_names } => { #object.#fn_name(#fn_args); }
}
});
quote! {{
use #event_type::*;
match #event_object {
#(#match_arms)*
_ => log::warn!(concat!("unhandled ", stringify!(#event_type), ": {:?}"), #event_object)
}
}}
.into()
}

View file

@ -1,5 +1,6 @@
use super::*; use super::*;
use log::{debug, error, trace, warn}; use log::{debug, error, trace, warn};
use macros::simple_event_shunt;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use wayland_client::globals::Global; use wayland_client::globals::Global;
use wayland_protocols::{ use wayland_protocols::{

View file

@ -1,5 +1,6 @@
use super::*; use super::*;
use log::{debug, trace, warn}; use log::{debug, trace, warn};
use macros::simple_event_shunt;
use std::collections::HashSet; use std::collections::HashSet;
use std::os::fd::AsFd; use std::os::fd::AsFd;
use wayland_client::{protocol as client, Proxy}; use wayland_client::{protocol as client, Proxy};
@ -35,79 +36,6 @@ use wayland_server::protocol::{
wl_seat::WlSeat, wl_touch::WlTouch, wl_seat::WlSeat, wl_touch::WlTouch,
}; };
/// Lord forgive me, I am a sinner, who's probably gonna sin again
/// This macro takes an enum variant name and a list of the field names of the enum
/// and/or closures that take an argument that must be named the same as the field name,
/// and converts that into a destructured enum
/// shunt_helper_enum!(Foo [a, |b| b.do_thing(), c]) -> Foo {a, b, c}
macro_rules! shunt_helper_enum {
// No fields
($variant:ident) => { $variant };
// Starting state: variant destructure
($variant:ident $([$($body:tt)+])?) => {
shunt_helper_enum!($variant [$($($body)+)?] -> [])
};
// Add field to list
($variant:ident [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
};
// Add closure field to list
($variant:ident [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
};
// Finalize into enum variant
($variant:ident [] -> [,$($body:tt)+]) => { $variant { $($body)+ } };
}
/// This does the same thing as shunt_helper_enum, except it transforms the fields into the given
/// function/method call.
/// shunt_helper_fn!({obj.foo} [a, |b| b.do_thing(), c]) -> obj.foo(a, b.do_thing(), c)
macro_rules! shunt_helper_fn {
// No fields
({$($fn:tt)+}) => { $($fn)+() };
// Starting state
($fn:tt [$($body:tt)+]) => {
shunt_helper_fn!($fn [$($body)+] -> [])
};
// Add field to list
($fn:tt [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $field])
};
// Add closure expression to list
($fn:tt [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $conv])
};
// Finalize into function call
({$($fn:tt)+} [] -> [,$($body:tt)+]) => { $($fn)+($($body)+) };
}
/// Takes an object, the name of a variable holding an event, the event type, and a list of the
/// variants with their fields, and converts them into function calls on their arguments
/// Event { field1, field2 } => obj.event(field1, field2)
macro_rules! simple_event_shunt {
($obj:expr, $event:ident: $event_type:path => [
$( $variant:ident $({ $($fields:tt)* })? ),+
]) => {
{
use $event_type::*;
match $event {
$(
shunt_helper_enum!( $variant $( [ $($fields)* ] )? ) => {
paste::paste! {
shunt_helper_fn!( { $obj.[<$variant:snake>] } $( [ $($fields)* ] )? )
}
}
)+
_ => log::warn!(concat!("unhandled ", stringify!($event_type), ": {:?}"), $event)
}
}
}
}
pub(crate) use shunt_helper_enum;
pub(crate) use shunt_helper_fn;
pub(crate) use simple_event_shunt;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum SurfaceEvents { pub(crate) enum SurfaceEvents {
WlSurface(client::wl_surface::Event), WlSurface(client::wl_surface::Event),