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"
|
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",
|
||||||
|
|
|
||||||
|
|
@ -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
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 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::{
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue