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:
parent
b988762955
commit
73ca9c91f1
6 changed files with 161 additions and 77 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
11
macros/Cargo.toml
Normal file
11
macros/Cargo.toml
Normal 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
133
macros/src/lib.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -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::{
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue