From 73ca9c91f173eb0f0147dbf62085caaaf93fabdf Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Tue, 15 Oct 2024 02:17:20 -0400 Subject: [PATCH] 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. --- Cargo.lock | 17 ++++-- Cargo.toml | 2 + macros/Cargo.toml | 11 ++++ macros/src/lib.rs | 133 +++++++++++++++++++++++++++++++++++++++++ src/server/dispatch.rs | 1 + src/server/event.rs | 74 +---------------------- 6 files changed, 161 insertions(+), 77 deletions(-) create mode 100644 macros/Cargo.toml create mode 100644 macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 779d96c..2865029 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,6 +305,14 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "memchr" version = "2.7.2" @@ -403,9 +411,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -516,9 +524,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -859,6 +867,7 @@ dependencies = [ "env_logger 0.11.3", "libc", "log", + "macros", "paste", "pretty_env_logger", "rustix", diff --git a/Cargo.toml b/Cargo.toml index d34bf94..bb452e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +members = ["macros"] [workspace.dependencies] wayland-client = "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 } sd-notify = { version = "0.4.2", optional = true } +macros = { version = "0.1.0", path = "macros" } [features] default = [] diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..33d3b94 --- /dev/null +++ b/macros/Cargo.toml @@ -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" diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..62963be --- /dev/null +++ b/macros/src/lib.rs @@ -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 { + 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>, +} + +impl Parse for EventVariant { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + 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, +} + +impl Parse for Input { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let object = input.parse()?; + input.parse::()?; + let event_object = input.parse()?; + input.parse::()?; + let event_type = input.parse()?; + input.parse::]>()?; + 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::::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::>(), + ) + .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() +} diff --git a/src/server/dispatch.rs b/src/server/dispatch.rs index 99fa4c5..a2cc7e1 100644 --- a/src/server/dispatch.rs +++ b/src/server/dispatch.rs @@ -1,5 +1,6 @@ use super::*; use log::{debug, error, trace, warn}; +use macros::simple_event_shunt; use std::sync::{Arc, OnceLock}; use wayland_client::globals::Global; use wayland_protocols::{ diff --git a/src/server/event.rs b/src/server/event.rs index b547f7d..c064441 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -1,5 +1,6 @@ use super::*; use log::{debug, trace, warn}; +use macros::simple_event_shunt; use std::collections::HashSet; use std::os::fd::AsFd; use wayland_client::{protocol as client, Proxy}; @@ -35,79 +36,6 @@ use wayland_server::protocol::{ 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)] pub(crate) enum SurfaceEvents { WlSurface(client::wl_surface::Event),