mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-03 18:24:09 +02:00
move event parsing to derive macros
This commit is contained in:
parent
b7d189b2a0
commit
14c1fbe0d9
13 changed files with 591 additions and 460 deletions
17
derive/Cargo.toml
Normal file
17
derive/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "tf-log-parser-derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "tf_log_parser_derive"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
syn_util = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tf-log-parser = { version = "0.1", path = ".." }
|
||||
23
derive/examples/expand.rs
Normal file
23
derive/examples/expand.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use tf_log_parser::{
|
||||
event::{param_parse, parse_field, quoted, ParamIter},
|
||||
Event, IResult,
|
||||
};
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct DamageEvent<'a> {
|
||||
#[event(name = "against")]
|
||||
pub target: RawSubject<'a>,
|
||||
pub damage: Option<NonZeroU32>,
|
||||
#[event(name = "realdamage")]
|
||||
pub real_damage: Option<NonZeroU32>,
|
||||
pub weapon: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct ShotFiredEvent {
|
||||
#[event(quoted)]
|
||||
pub weapon: u32,
|
||||
pub damage: Option<u32>,
|
||||
}
|
||||
|
||||
pub fn main() {}
|
||||
233
derive/src/event.rs
Normal file
233
derive/src/event.rs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
use crate::{Derivable, DeriveParams};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{Data, DeriveInput, Error, Field, Fields, Generics, Lifetime, Result, Type, TypePath};
|
||||
use syn_util::{contains_attribute, get_attribute_value};
|
||||
|
||||
macro_rules! bail {
|
||||
($msg:expr $(,)?) => {
|
||||
return Err(Error::new(Span::call_site(), &$msg[..]))
|
||||
};
|
||||
|
||||
( $msg:expr => $span_to_blame:expr $(,)? ) => {
|
||||
return Err(Error::new_spanned(&$span_to_blame, $msg))
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Event;
|
||||
|
||||
impl Derivable for Event {
|
||||
type Params = EventParams;
|
||||
|
||||
fn derive(params: EventParams) -> Result<TokenStream> {
|
||||
let struct_ident = params.name;
|
||||
let span = struct_ident.span();
|
||||
let required_params = params.params.iter().filter(|param| !param.optional);
|
||||
let optional_params = params.params.iter().filter(|param| param.optional);
|
||||
let has_optional = params.params.iter().any(|param| param.optional);
|
||||
|
||||
let required_fields = required_params
|
||||
.map(|param| {
|
||||
let field_name = ¶m.field_name;
|
||||
|
||||
let parser = match (¶m.param_name, param.quoted) {
|
||||
(Some(param_name), true) => {
|
||||
quote_spanned!(field_name.span() => param_parse_with(#param_name, quoted(parse_field)))
|
||||
}
|
||||
(Some(param_name), false) => {
|
||||
quote_spanned!(field_name.span() => param_parse_with(#param_name, parse_field))
|
||||
}
|
||||
(None, true) => {
|
||||
quote_spanned!(field_name.span() => quoted(parse_field))
|
||||
}
|
||||
(None, false) => {
|
||||
quote_spanned!(field_name.span() => parse_field)
|
||||
}
|
||||
};
|
||||
|
||||
let skip_after = param.skip_after as usize;
|
||||
let after = if skip_after > 0 {
|
||||
quote_spanned!(field_name.span() => let input = &input[#skip_after..];)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
Ok(quote_spanned!(field_name.span() =>
|
||||
#[allow(unused_variables)]
|
||||
let (input, #field_name) = #parser(input)?;
|
||||
#after
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let initiators = params.params.iter().map(|param| {
|
||||
let field_name = ¶m.field_name;
|
||||
|
||||
if param.optional {
|
||||
quote_spanned!(field_name.span() => #field_name: Default::default())
|
||||
} else {
|
||||
quote_spanned!(field_name.span() => #field_name)
|
||||
}
|
||||
});
|
||||
let initiator = quote!(
|
||||
#[allow(unused_mut)]
|
||||
let mut event = #struct_ident {
|
||||
#(#initiators),*
|
||||
};
|
||||
);
|
||||
let update = if has_optional {
|
||||
let matches = optional_params
|
||||
.map(|param| {
|
||||
let field_name = ¶m.field_name;
|
||||
let Some(param_name) = param.param_name.as_deref() else {
|
||||
bail!("optional fields can't be unnamed" => param.field_name)
|
||||
};
|
||||
|
||||
let parser = if param.quoted {
|
||||
quote_spanned!(field_name.span() => quoted(parse_field))
|
||||
} else {
|
||||
quote_spanned!(field_name.span() => parse_field)
|
||||
};
|
||||
|
||||
Ok(quote_spanned!(
|
||||
field_name.span() => #param_name => event.#field_name = #parser(value)?.1
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
quote_spanned!(span => for (key, value) in ParamIter::new(input) {
|
||||
match key {
|
||||
#(#matches,)*
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = params.generics.split_for_impl();
|
||||
|
||||
let lifetime = params.lifetime;
|
||||
|
||||
Ok(
|
||||
quote_spanned!(span => impl #impl_generics Event<#lifetime> for #struct_ident #ty_generics #where_clause {
|
||||
fn parse(input: & #lifetime str) -> IResult<Self> {
|
||||
#(#required_fields)*
|
||||
|
||||
#initiator
|
||||
|
||||
#update
|
||||
|
||||
Ok(("", event))
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventParams {
|
||||
name: Ident,
|
||||
lifetime: Lifetime,
|
||||
generics: Generics,
|
||||
params: Vec<EventParam>,
|
||||
}
|
||||
|
||||
impl DeriveParams for EventParams {
|
||||
fn parse(input: &DeriveInput) -> Result<EventParams> {
|
||||
let Data::Struct(data) = &input.data else {
|
||||
bail!("only supported on structs" => input)
|
||||
};
|
||||
let Fields::Named(fields) = &data.fields else {
|
||||
bail!("only supported with named fields" => input)
|
||||
};
|
||||
let name = input.ident.clone();
|
||||
let generics = input.generics.clone();
|
||||
let params = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(EventParam::parse)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let mut last_optional = false;
|
||||
for param in params.iter() {
|
||||
if last_optional > param.optional {
|
||||
bail!("optional fields are required to be at the end" => param.field_name)
|
||||
}
|
||||
last_optional = param.optional;
|
||||
}
|
||||
|
||||
let lifetime = if let Some(lifetime) =
|
||||
get_attribute_value::<String>(&input.attrs, &["event", "lifetime"])
|
||||
{
|
||||
Lifetime::new(&lifetime, name.span())
|
||||
} else {
|
||||
let mut lifetimes = input.generics.lifetimes();
|
||||
let lifetime = lifetimes
|
||||
.next()
|
||||
.cloned()
|
||||
.map(|lifetime| lifetime.lifetime)
|
||||
.unwrap_or_else(|| Lifetime::new("'_", name.span()));
|
||||
if lifetimes.next().is_some() {
|
||||
bail!("For structs with more than one lifetime, manually specifiying the lifetime is required" => name);
|
||||
}
|
||||
lifetime
|
||||
};
|
||||
|
||||
Ok(EventParams {
|
||||
name,
|
||||
lifetime,
|
||||
generics,
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventParam {
|
||||
field_name: Ident,
|
||||
param_name: Option<String>,
|
||||
optional: bool,
|
||||
skip_after: u64,
|
||||
quoted: bool,
|
||||
}
|
||||
|
||||
impl EventParam {
|
||||
pub fn parse(input: &Field) -> Result<EventParam> {
|
||||
let field_name = input.ident.clone().expect("no name on named fields");
|
||||
let param_name = if contains_attribute(&input.attrs, &["event", "unnamed"]) {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
get_attribute_value(&input.attrs, &["event", "name"])
|
||||
.unwrap_or_else(|| field_name.to_string()),
|
||||
)
|
||||
};
|
||||
let is_option = match &input.ty {
|
||||
Type::Path(TypePath { path, .. }) => {
|
||||
path.segments
|
||||
.first()
|
||||
.map(|segment| segment.ident.to_string())
|
||||
.as_deref()
|
||||
== Some("Option")
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let optional = is_option || contains_attribute(&input.attrs, &["event", "default"]);
|
||||
let skip_after =
|
||||
get_attribute_value(&input.attrs, &["event", "skip_after"]).unwrap_or_default();
|
||||
|
||||
if optional && skip_after > 0 {
|
||||
bail!("skip_after can't be used with optional fields" => input);
|
||||
}
|
||||
let quoted = contains_attribute(&input.attrs, &["event", "quoted"]);
|
||||
|
||||
Ok(EventParam {
|
||||
field_name,
|
||||
param_name,
|
||||
optional,
|
||||
skip_after,
|
||||
quoted,
|
||||
})
|
||||
}
|
||||
}
|
||||
38
derive/src/lib.rs
Normal file
38
derive/src/lib.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
//! Derive macros for tf-log-parser
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
mod event;
|
||||
|
||||
use crate::event::Event;
|
||||
use proc_macro2::TokenStream;
|
||||
use std::fmt::Debug;
|
||||
use syn::{parse_macro_input, DeriveInput, Result};
|
||||
|
||||
/// Derive the `Event` trait for a struct
|
||||
#[proc_macro_derive(Event, attributes(event))]
|
||||
pub fn derive_pod(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let expanded = derive_trait::<Event>(parse_macro_input!(input as DeriveInput));
|
||||
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
/// Basic wrapper for error handling
|
||||
fn derive_trait<Trait: Derivable>(input: DeriveInput) -> TokenStream {
|
||||
derive_trait_inner::<Trait>(input).unwrap_or_else(|err| err.into_compile_error())
|
||||
}
|
||||
|
||||
fn derive_trait_inner<Trait: Derivable>(input: DeriveInput) -> Result<TokenStream> {
|
||||
let params = Trait::Params::parse(&input)?;
|
||||
Trait::derive(params)
|
||||
}
|
||||
|
||||
trait Derivable {
|
||||
type Params: DeriveParams;
|
||||
|
||||
fn derive(params: Self::Params) -> Result<TokenStream>;
|
||||
}
|
||||
|
||||
trait DeriveParams: Sized + Debug {
|
||||
fn parse(input: &DeriveInput) -> Result<Self>;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue