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
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
/target
|
||||
target
|
||||
/result
|
||||
/.direnv
|
||||
Cargo.lock
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ paste = "1"
|
|||
logos = "0.12"
|
||||
memchr = "2.5.0"
|
||||
ahash = "0.8.3"
|
||||
tf-log-parser-derive = { version = "0.1", path = "./derive" }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.4"
|
||||
|
|
|
|||
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>;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::event::EventFieldFromStr;
|
||||
use crate::raw_event::{split_player_subject, RawSubject};
|
||||
use crate::{Error, Result};
|
||||
use enum_iterator::{all, Sequence};
|
||||
|
|
@ -18,6 +19,8 @@ pub enum Team {
|
|||
Spectator,
|
||||
}
|
||||
|
||||
impl EventFieldFromStr for Team {}
|
||||
|
||||
impl Default for Team {
|
||||
fn default() -> Self {
|
||||
Team::Spectator
|
||||
|
|
@ -67,6 +70,8 @@ pub enum Class {
|
|||
Spy,
|
||||
}
|
||||
|
||||
impl EventFieldFromStr for Class {}
|
||||
|
||||
impl Class {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
|
|
@ -94,15 +99,15 @@ impl FromStr for Class {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"scout" => Ok(Class::Scout),
|
||||
"soldier" => Ok(Class::Soldier),
|
||||
"pyro" => Ok(Class::Pyro),
|
||||
"demoman" => Ok(Class::DemoMan),
|
||||
"heavyweapons" => Ok(Class::HeavyWeapons),
|
||||
"engineer" => Ok(Class::Engineer),
|
||||
"medic" => Ok(Class::Medic),
|
||||
"sniper" => Ok(Class::Sniper),
|
||||
"spy" => Ok(Class::Spy),
|
||||
"Scout" | "scout" => Ok(Class::Scout),
|
||||
"Soldier" | "soldier" => Ok(Class::Soldier),
|
||||
"Pyro" | "pyro" => Ok(Class::Pyro),
|
||||
"Demoman" | "demoman" => Ok(Class::DemoMan),
|
||||
"Heavyweapons" | "heavyweapons" | "Heavy" | "heavy" => Ok(Class::HeavyWeapons),
|
||||
"Engineer" | "engineer" => Ok(Class::Engineer),
|
||||
"Medic" | "medic" => Ok(Class::Medic),
|
||||
"Sniper" | "sniper" => Ok(Class::Sniper),
|
||||
"Spy" | "spy" => Ok(Class::Spy),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,89 +1,53 @@
|
|||
use crate::event::{param_parse, param_parse_with, position, u_int, ParamIter};
|
||||
use crate::raw_event::{against_subject_parser, RawSubject};
|
||||
use crate::event::{param_parse, param_parse_with, parse_field, ParamIter};
|
||||
use crate::raw_event::RawSubject;
|
||||
use crate::{Event, IResult};
|
||||
|
||||
use nom::bytes::complete::{tag, take_while};
|
||||
use nom::combinator::opt;
|
||||
use nom::number::complete::float;
|
||||
use nom::IResult;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct RoundWinEvent<'a> {
|
||||
#[event(name = "winner")]
|
||||
pub team: Option<&'a str>,
|
||||
}
|
||||
|
||||
pub fn round_win_event_parser(input: &str) -> IResult<&str, RoundWinEvent> {
|
||||
let (input, team) = opt(param_parse("against"))(input)?;
|
||||
Ok((input, RoundWinEvent { team }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct RoundLengthEvent {
|
||||
#[event(name = "seconds")]
|
||||
pub length: Option<f32>,
|
||||
}
|
||||
|
||||
pub fn round_length_event_parser(input: &str) -> IResult<&str, RoundLengthEvent> {
|
||||
let (input, length) = opt(param_parse_with("against", float))(input)?;
|
||||
Ok((input, RoundLengthEvent { length }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct LogFileStartedEvent<'a> {
|
||||
pub file: Option<&'a str>,
|
||||
pub game: Option<&'a str>,
|
||||
pub version: Option<&'a str>,
|
||||
}
|
||||
|
||||
pub fn log_file_started_event_parser(input: &str) -> IResult<&str, LogFileStartedEvent> {
|
||||
let (input, file) = opt(param_parse("file"))(input)?;
|
||||
let (input, game) = opt(param_parse("game"))(input)?;
|
||||
let (input, version) = opt(param_parse("version"))(input)?;
|
||||
Ok((
|
||||
input,
|
||||
LogFileStartedEvent {
|
||||
file,
|
||||
game,
|
||||
version,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TournamentModeStartedEvent<'a> {
|
||||
pub blue: &'a str,
|
||||
pub red: &'a str,
|
||||
}
|
||||
|
||||
pub fn tournament_mode_started_event_parser(
|
||||
input: &str,
|
||||
) -> IResult<&str, TournamentModeStartedEvent> {
|
||||
impl<'a> Event<'a> for TournamentModeStartedEvent<'a> {
|
||||
fn parse(input: &'a str) -> IResult<Self> {
|
||||
let (input, _) = tag("\nBlue Team: ")(input)?;
|
||||
let (input, blue) = take_while(|c| c != '\n')(input)?;
|
||||
let (input, _) = tag("\nRed Team: ")(input)?;
|
||||
let (input, red) = take_while(|c| c != '\n')(input)?;
|
||||
Ok((input, TournamentModeStartedEvent { blue, red }))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct CaptureBlockedEvent<'a> {
|
||||
pub cp: Option<u8>,
|
||||
#[event(name = "cpname")]
|
||||
pub cp_name: Option<&'a str>,
|
||||
pub position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn capture_blocked_event_parser(input: &str) -> IResult<&str, CaptureBlockedEvent> {
|
||||
let (input, cp) = opt(param_parse_with("cp", u_int))(input)?;
|
||||
let (input, cp_name) = opt(param_parse("map"))(input)?;
|
||||
let (input, position) = opt(param_parse_with("red", position))(input)?;
|
||||
Ok((
|
||||
input,
|
||||
CaptureBlockedEvent {
|
||||
cp: cp.map(|cp| cp as u8),
|
||||
cp_name,
|
||||
position,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PointCapturedEvent<'a> {
|
||||
pub cp: Option<u8>,
|
||||
|
|
@ -92,10 +56,11 @@ pub struct PointCapturedEvent<'a> {
|
|||
pub players: Vec<(RawSubject<'a>, (i32, i32, i32))>,
|
||||
}
|
||||
|
||||
pub fn point_captures_event_parser(input: &str) -> IResult<&str, PointCapturedEvent> {
|
||||
let (input, cp) = opt(param_parse_with("cp", u_int))(input)?;
|
||||
let (input, cp_name) = opt(param_parse("map"))(input)?;
|
||||
let (input, num_cappers) = opt(param_parse_with("numcappers", u_int))(input)?;
|
||||
impl<'a> Event<'a> for PointCapturedEvent<'a> {
|
||||
fn parse(input: &'a str) -> IResult<Self> {
|
||||
let (input, cp) = opt(param_parse("cp"))(input)?;
|
||||
let (input, cp_name) = opt(param_parse("cpname"))(input)?;
|
||||
let (input, num_cappers) = opt(param_parse("numcappers"))(input)?;
|
||||
|
||||
let mut players = Vec::new();
|
||||
|
||||
|
|
@ -104,9 +69,7 @@ pub fn point_captures_event_parser(input: &str) -> IResult<&str, PointCapturedEv
|
|||
(Some((subject_key, subject)), Some((position_key, position_str)))
|
||||
if subject_key.starts_with("player") && position_key.starts_with("position") =>
|
||||
{
|
||||
let (_, subject) = against_subject_parser(subject)?;
|
||||
let (_, position) = position(position_str)?;
|
||||
players.push((subject, position));
|
||||
players.push((parse_field(subject)?.1, parse_field(position_str)?.1));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -114,56 +77,32 @@ pub fn point_captures_event_parser(input: &str) -> IResult<&str, PointCapturedEv
|
|||
Ok((
|
||||
input,
|
||||
PointCapturedEvent {
|
||||
cp: cp.map(|cp| cp as u8),
|
||||
num_cappers: num_cappers.map(|num| num as u8),
|
||||
cp,
|
||||
num_cappers,
|
||||
cp_name,
|
||||
players,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct CurrentScoreEvent {
|
||||
#[event(unnamed)]
|
||||
pub score: u8,
|
||||
pub player: u8,
|
||||
#[event(name = "with")]
|
||||
pub players: u8,
|
||||
}
|
||||
|
||||
pub fn current_score_event_parser(input: &str) -> IResult<&str, CurrentScoreEvent> {
|
||||
let (input, score) = param_parse_with("cp", u_int)(input)?;
|
||||
let (input, player) = param_parse_with("with", u_int)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
CurrentScoreEvent {
|
||||
score: score as u8,
|
||||
player: player as u8,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct GameOverEvent<'a> {
|
||||
pub reason: &'a str,
|
||||
}
|
||||
|
||||
pub fn game_over_event_parser(input: &str) -> IResult<&str, GameOverEvent> {
|
||||
let (input, reason) = param_parse("reason")(input)?;
|
||||
Ok((input, GameOverEvent { reason }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct FinalScoreEvent {
|
||||
#[event(unnamed)]
|
||||
pub score: u8,
|
||||
pub player: u8,
|
||||
}
|
||||
|
||||
pub fn final_score_event_parser(input: &str) -> IResult<&str, FinalScoreEvent> {
|
||||
let (input, score) = param_parse_with("cp", u_int)(input)?;
|
||||
let (input, player) = param_parse_with("with", u_int)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
FinalScoreEvent {
|
||||
score: score as u8,
|
||||
player: player as u8,
|
||||
},
|
||||
))
|
||||
#[event(name = "with")]
|
||||
pub players: u8,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +1,43 @@
|
|||
use crate::event::{param_parse, param_parse_with, quoted, u_int, ParamIter};
|
||||
use crate::raw_event::{against_subject_parser, RawSubject};
|
||||
use nom::combinator::opt;
|
||||
use nom::number::complete::float;
|
||||
use nom::IResult;
|
||||
use crate::event::{param_parse_with, parse_field, quoted, ParamIter};
|
||||
use crate::raw_event::RawSubject;
|
||||
use crate::{Event, IResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct HealedEvent<'a> {
|
||||
#[event(name = "against")]
|
||||
pub target: RawSubject<'a>,
|
||||
#[event(name = "healing")]
|
||||
#[event(quoted)]
|
||||
pub amount: u32,
|
||||
}
|
||||
|
||||
pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> {
|
||||
let (input, subject) = param_parse_with("against", against_subject_parser)(input)?;
|
||||
let (input, amount) = param_parse_with("healing", quoted(u_int))(input)?;
|
||||
Ok((
|
||||
input,
|
||||
HealedEvent {
|
||||
target: subject,
|
||||
amount,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ChargeDeployedEvent<'a> {
|
||||
pub medigun: Option<&'a str>,
|
||||
}
|
||||
|
||||
pub fn charge_deployed_event_parser(input: &str) -> IResult<&str, ChargeDeployedEvent> {
|
||||
let (input, medigun) = opt(param_parse("healing"))(input)?;
|
||||
Ok((input, ChargeDeployedEvent { medigun }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ChargeEndedEvent {
|
||||
pub duration: Option<f32>,
|
||||
}
|
||||
|
||||
pub fn charge_ended_event_parser(input: &str) -> IResult<&str, ChargeEndedEvent> {
|
||||
let (input, duration) = opt(param_parse_with("duration", quoted(float)))(input)?;
|
||||
Ok((input, ChargeEndedEvent { duration }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct AdvantageLostEvent {
|
||||
pub time: Option<f32>,
|
||||
}
|
||||
|
||||
pub fn advantage_lost_event_parser(input: &str) -> IResult<&str, AdvantageLostEvent> {
|
||||
let (input, time) = opt(param_parse_with("time", quoted(float)))(input)?;
|
||||
Ok((input, AdvantageLostEvent { time }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct FirstHealEvent {
|
||||
pub time: Option<f32>,
|
||||
}
|
||||
|
||||
pub fn first_heal_event_parser(input: &str) -> IResult<&str, FirstHealEvent> {
|
||||
let (input, time) = opt(param_parse_with("time", quoted(float)))(input)?;
|
||||
Ok((input, FirstHealEvent { time }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct MedicDeathEvent {
|
||||
#[event(name = "ubercharge")]
|
||||
pub charge: Option<u32>,
|
||||
}
|
||||
|
||||
pub fn medic_death_event_parser(input: &str) -> IResult<&str, MedicDeathEvent> {
|
||||
let mut charge = None;
|
||||
for (key, value) in ParamIter::new(input) {
|
||||
if key == "ubercharge" {
|
||||
charge = Some(quoted(u_int)(value)?.1);
|
||||
}
|
||||
}
|
||||
Ok((input, MedicDeathEvent { charge }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct MedicDeathExEvent {
|
||||
pub charge_percentage: Option<u8>,
|
||||
}
|
||||
|
||||
pub fn medic_death_ex_event_parser(input: &str) -> IResult<&str, MedicDeathExEvent> {
|
||||
let (input, charge_percentage) = opt(param_parse_with("time", quoted(u_int)))(input)?;
|
||||
Ok((
|
||||
input,
|
||||
MedicDeathExEvent {
|
||||
charge_percentage: charge_percentage.map(|charge: u32| charge as u8),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
|
|
|||
232
src/event/mod.rs
232
src/event/mod.rs
|
|
@ -3,15 +3,19 @@ mod medic;
|
|||
mod player;
|
||||
|
||||
use crate::event::game::{RoundLengthEvent, RoundWinEvent};
|
||||
use crate::{RawEvent, RawEventType, SubjectId};
|
||||
use crate::raw_event::{against_subject_parser, RawSubject};
|
||||
use crate::{IResult, RawEvent, RawEventType, SubjectId};
|
||||
pub use game::*;
|
||||
pub use medic::*;
|
||||
use nom::bytes::complete::{tag, take_while};
|
||||
use nom::character::complete::{alpha1, digit1};
|
||||
use nom::combinator::opt;
|
||||
use nom::error::{ErrorKind, ParseError};
|
||||
use nom::{Err, IResult};
|
||||
use nom::number::complete::float;
|
||||
use nom::Err;
|
||||
pub use player::*;
|
||||
use std::net::SocketAddr;
|
||||
use std::num::NonZeroU32;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -30,7 +34,7 @@ trait GameEventErrTrait<T> {
|
|||
fn with_type(self, ty: RawEventType) -> Result<T, GameEventError>;
|
||||
}
|
||||
|
||||
impl<'a, T> GameEventErrTrait<T> for IResult<&str, T> {
|
||||
impl<'a, T> GameEventErrTrait<T> for IResult<'a, T> {
|
||||
fn with_type(self, ty: RawEventType) -> Result<T, GameEventError> {
|
||||
self.map_err(|err| match err {
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => GameEventError::Error {
|
||||
|
|
@ -47,6 +51,14 @@ impl<'a, T> GameEventErrTrait<T> for IResult<&str, T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Event<'a>: Sized + 'a {
|
||||
fn parse(input: &'a str) -> IResult<Self>;
|
||||
}
|
||||
|
||||
fn parse_event<'a, T: Event<'a>>(input: &'a str) -> IResult<T> {
|
||||
T::parse(input)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventMeta {
|
||||
pub time: u32,
|
||||
|
|
@ -104,116 +116,100 @@ impl<'a> GameEvent<'a> {
|
|||
pub fn parse(raw: &RawEvent<'a>) -> Result<GameEvent<'a>, GameEventError> {
|
||||
Ok(match raw.ty {
|
||||
RawEventType::ShotFired => {
|
||||
GameEvent::ShotFired(shot_fired_event_parser(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::ShotHit => {
|
||||
GameEvent::ShotHit(shot_hit_event_parser(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Damage => {
|
||||
GameEvent::Damage(damage_event_parser(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Killed => {
|
||||
GameEvent::Kill(kill_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::ShotFired(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::ShotHit => GameEvent::ShotHit(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::Damage => GameEvent::Damage(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::Killed => GameEvent::Kill(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::KillAssist => {
|
||||
GameEvent::KillAssist(kill_assist_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::KillAssist(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::SayTeam => GameEvent::SayTeam(raw.params.trim_matches('"')),
|
||||
RawEventType::Say => GameEvent::Say(raw.params.trim_matches('"')),
|
||||
RawEventType::Healed => {
|
||||
GameEvent::Healed(healed_event_parser(raw.params).with_type(raw.ty)?)
|
||||
RawEventType::Healed => GameEvent::Healed(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::ChargeDeployed => {
|
||||
GameEvent::ChargeDeployed(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::ChargeDeployed => GameEvent::ChargeDeployed(
|
||||
charge_deployed_event_parser(raw.params).with_type(raw.ty)?,
|
||||
),
|
||||
RawEventType::ChargeEnd => {
|
||||
GameEvent::ChargeEnded(charge_ended_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::ChargeEnded(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::UberAdvantageLost => {
|
||||
GameEvent::AdvantageLost(advantage_lost_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::AdvantageLost(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::FirstHealAfterSpawn => {
|
||||
GameEvent::FirstHeal(first_heal_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::FirstHeal(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::ChargeReady => GameEvent::ChargeReady,
|
||||
RawEventType::MedicDeath => {
|
||||
GameEvent::MedicDeath(medic_death_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::MedicDeath(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::MedicDeathEx => {
|
||||
GameEvent::MedicDeathEx(medic_death_ex_event_parser(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Spawned => {
|
||||
GameEvent::Spawned(spawn_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::MedicDeathEx(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Spawned => GameEvent::Spawned(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::ChangedRole => {
|
||||
GameEvent::RoleChange(role_changed_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::RoleChange(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::RoundStart => GameEvent::RoundStart,
|
||||
RawEventType::RoundLength => {
|
||||
GameEvent::RoundLength(round_length_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::RoundLength(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::RoundWin => {
|
||||
GameEvent::RoundWin(round_win_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::RoundWin(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::RoundOvertime => GameEvent::RoundOverTime,
|
||||
RawEventType::LogFileStarted => GameEvent::LogFileStarted(
|
||||
log_file_started_event_parser(raw.params).with_type(raw.ty)?,
|
||||
),
|
||||
RawEventType::LogFileStarted => {
|
||||
GameEvent::LogFileStarted(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Connected => {
|
||||
GameEvent::Connected(connected_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::Connected(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Disconnected => {
|
||||
GameEvent::Disconnect(disconnected_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::Disconnect(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::SteamIdValidated => GameEvent::SteamIdValidated,
|
||||
RawEventType::Entered => GameEvent::Entered,
|
||||
RawEventType::Joined => {
|
||||
GameEvent::Joined(joined_team_event_parser(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Suicide => {
|
||||
GameEvent::Suicide(committed_suicide_event_parser(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Joined => GameEvent::Joined(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::Suicide => GameEvent::Suicide(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::PickedUp => {
|
||||
GameEvent::PickedUp(picked_up_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::PickedUp(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Domination => {
|
||||
GameEvent::Domination(domination_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::Domination(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::EmptyUber => GameEvent::EmptyUber,
|
||||
RawEventType::Revenge => {
|
||||
GameEvent::Revenge(revenge_event_parser(raw.params).with_type(raw.ty)?)
|
||||
RawEventType::Revenge => GameEvent::Revenge(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::TournamentStart => {
|
||||
GameEvent::TournamentModeStarted(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::CaptureBlocked => {
|
||||
GameEvent::CaptureBlocked(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::TournamentStart => GameEvent::TournamentModeStarted(
|
||||
tournament_mode_started_event_parser(raw.params).with_type(raw.ty)?,
|
||||
),
|
||||
RawEventType::CaptureBlocked => GameEvent::CaptureBlocked(
|
||||
capture_blocked_event_parser(raw.params).with_type(raw.ty)?,
|
||||
),
|
||||
RawEventType::PointCaptured => {
|
||||
GameEvent::PointCaptured(point_captures_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::PointCaptured(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::CurrentScore => {
|
||||
GameEvent::CurrentScore(current_score_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::CurrentScore(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::PlayerBuiltObject => {
|
||||
GameEvent::BuiltObject(built_object_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::BuiltObject(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::PlayerKilledObject => {
|
||||
GameEvent::KilledObject(killed_object_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::KilledObject(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::PlayerExtinguished => {
|
||||
GameEvent::Extinguished(extinguished_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::Extinguished(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::GameOver => {
|
||||
GameEvent::GameOver(game_over_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::GameOver(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::FinalScore => {
|
||||
GameEvent::FinalScore(final_score_event_parser(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::FinalScore(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::LogFileClosed => GameEvent::LogFileClosed,
|
||||
RawEventType::ObjectDetonated => GameEvent::ObjectDetonated(
|
||||
object_detonated_event_parser(raw.params).with_type(raw.ty)?,
|
||||
),
|
||||
RawEventType::ObjectDetonated => {
|
||||
GameEvent::ObjectDetonated(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
_ => {
|
||||
todo!("{:?} not parsed yet", raw.ty);
|
||||
}
|
||||
|
|
@ -221,7 +217,7 @@ impl<'a> GameEvent<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
struct ParamIter<'a> {
|
||||
pub struct ParamIter<'a> {
|
||||
input: &'a str,
|
||||
}
|
||||
|
||||
|
|
@ -241,7 +237,7 @@ impl<'a> Iterator for ParamIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn param_pair_parse(input: &str) -> IResult<&str, (&str, &str)> {
|
||||
fn param_pair_parse(input: &str) -> IResult<'_, (&str, &str)> {
|
||||
let (input, open_tag) = opt(tag("("))(input)?;
|
||||
|
||||
let (input, key) = alpha1(input)?;
|
||||
|
|
@ -255,9 +251,9 @@ fn param_pair_parse(input: &str) -> IResult<&str, (&str, &str)> {
|
|||
Ok((input, (key, value)))
|
||||
}
|
||||
|
||||
fn quoted<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
|
||||
fn quoted<'a, T, P: Fn(&'a str) -> IResult<'a, T>>(
|
||||
parser: P,
|
||||
) -> impl Fn(&'a str) -> IResult<&'a str, T> {
|
||||
) -> impl Fn(&'a str) -> IResult<'a, T> {
|
||||
move |input| {
|
||||
let (input, _) = tag(r#"""#)(input)?;
|
||||
let (input, res) = parser(input)?;
|
||||
|
|
@ -266,16 +262,17 @@ fn quoted<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
|
|||
}
|
||||
}
|
||||
|
||||
fn param_parse<'a>(key: &'a str) -> impl Fn(&'a str) -> IResult<&'a str, &'a str> {
|
||||
param_parse_with(key, quoted(take_while(|c| c != '"')))
|
||||
pub fn param_parse<'a, T: EventField<'a>>(key: &'a str) -> impl Fn(&'a str) -> IResult<'a, T> {
|
||||
param_parse_with(key, T::parse_field)
|
||||
}
|
||||
|
||||
fn param_parse_with<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
|
||||
pub fn param_parse_with<'a, T, P: Fn(&'a str) -> IResult<'a, T>>(
|
||||
key: &'a str,
|
||||
parser: P,
|
||||
) -> impl Fn(&'a str) -> IResult<&'a str, T> {
|
||||
) -> impl Fn(&'a str) -> IResult<'a, T> {
|
||||
move |input: &str| {
|
||||
debug_assert!(input.as_bytes()[0] != b' ');
|
||||
|
||||
let has_open = input.as_bytes()[0] == b'(';
|
||||
let input = &input[has_open as usize..];
|
||||
|
||||
|
|
@ -296,13 +293,13 @@ fn param_parse_with<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_from_str<T: FromStr>(input: &str) -> IResult<&str, T> {
|
||||
fn parse_from_str<'a, T: FromStr + 'a>(input: &'a str) -> IResult<T> {
|
||||
T::from_str(input)
|
||||
.map(|res| ("", res))
|
||||
.map_err(|_| nom::Err::Error(nom::error::Error::from_error_kind(input, ErrorKind::IsNot)))
|
||||
}
|
||||
|
||||
fn int(input: &str) -> IResult<&str, i32> {
|
||||
fn int(input: &str) -> IResult<i32> {
|
||||
let (input, sign) = opt(tag("-"))(input)?;
|
||||
let (input, raw) = digit1(input)?;
|
||||
let val: i32 = raw
|
||||
|
|
@ -311,15 +308,23 @@ fn int(input: &str) -> IResult<&str, i32> {
|
|||
Ok((input, if sign.is_some() { -val } else { val }))
|
||||
}
|
||||
|
||||
fn u_int(input: &str) -> IResult<&str, u32> {
|
||||
fn u_int(input: &str) -> IResult<u32> {
|
||||
let (input, quote) = opt(tag("\""))(input)?;
|
||||
|
||||
let (input, raw) = digit1(input)?;
|
||||
let val = raw
|
||||
.parse()
|
||||
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw, ErrorKind::Digit)))?;
|
||||
|
||||
let input = if quote.is_some() {
|
||||
tag("\"")(input)?.0
|
||||
} else {
|
||||
input
|
||||
};
|
||||
Ok((input, val))
|
||||
}
|
||||
|
||||
fn position(input: &str) -> IResult<&str, (i32, i32, i32)> {
|
||||
pub fn position(input: &str) -> IResult<(i32, i32, i32)> {
|
||||
let (input, x) = int(input)?;
|
||||
let (input, _) = tag(" ")(input)?;
|
||||
let (input, y) = int(input)?;
|
||||
|
|
@ -327,3 +332,84 @@ fn position(input: &str) -> IResult<&str, (i32, i32, i32)> {
|
|||
let (input, z) = int(input)?;
|
||||
Ok((input, (x, y, z)))
|
||||
}
|
||||
|
||||
pub trait EventField<'a>: Sized + 'a {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self>;
|
||||
}
|
||||
|
||||
impl<'a> EventField<'a> for &'a str {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
if input.starts_with('"') {
|
||||
quoted(take_while(|c| c != '"'))(input)
|
||||
} else {
|
||||
Ok(("", input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventField<'a> for i32 {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
int(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventField<'a> for u32 {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
u_int(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventField<'a> for f32 {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
float(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventField<'a> for u8 {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
u_int(input).map(|(rest, num)| (rest, num as u8))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EventField<'a>> EventField<'a> for Option<T> {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
T::parse_field(input).map(|(rest, int)| (rest, Some(int)))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventFieldFromStr: FromStr {}
|
||||
|
||||
impl<'a, T: EventFieldFromStr + 'a> EventField<'a> for T {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
parse_from_str(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventFieldFromStr for SocketAddr {}
|
||||
|
||||
impl<'a, T: EventField<'a>> EventField<'a> for (T, T, T) {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
let (input, x) = T::parse_field(input)?;
|
||||
let (input, _) = tag(" ")(input)?;
|
||||
let (input, y) = T::parse_field(input)?;
|
||||
let (input, _) = tag(" ")(input)?;
|
||||
let (input, z) = T::parse_field(input)?;
|
||||
Ok((input, (x, y, z)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventField<'a> for Option<NonZeroU32> {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
u32::parse_field(input).map(|(rest, int)| (rest, NonZeroU32::new(int)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventField<'a> for RawSubject<'a> {
|
||||
fn parse_field(input: &'a str) -> crate::IResult<Self> {
|
||||
against_subject_parser(input)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_field<'a, T: EventField<'a>>(input: &'a str) -> crate::IResult<T> {
|
||||
T::parse_field(input)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,286 +1,124 @@
|
|||
use crate::common::{Class, Team};
|
||||
use crate::event::{param_parse, param_parse_with, parse_from_str, position, u_int, ParamIter};
|
||||
use crate::raw_event::{against_subject_parser, RawSubject};
|
||||
use nom::combinator::opt;
|
||||
use nom::IResult;
|
||||
use crate::event::{param_parse_with, parse_field, ParamIter};
|
||||
use crate::raw_event::RawSubject;
|
||||
use crate::{Event, IResult};
|
||||
use std::net::SocketAddr;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ShotFiredEvent<'a> {
|
||||
pub weapon: Option<&'a str>,
|
||||
}
|
||||
|
||||
pub fn shot_fired_event_parser(input: &str) -> IResult<&str, ShotFiredEvent> {
|
||||
let (input, weapon) = opt(param_parse("weapon"))(input)?;
|
||||
Ok((input, ShotFiredEvent { weapon }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ShotHitEvent<'a> {
|
||||
pub weapon: Option<&'a str>,
|
||||
}
|
||||
|
||||
pub fn shot_hit_event_parser(input: &str) -> IResult<&str, ShotHitEvent> {
|
||||
let (input, weapon) = opt(param_parse("weapon"))(input)?;
|
||||
Ok((input, ShotHitEvent { weapon }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, 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>,
|
||||
}
|
||||
|
||||
pub fn damage_event_parser(input: &str) -> IResult<&str, DamageEvent> {
|
||||
let (input, target) = param_parse_with("against", against_subject_parser)(input)?;
|
||||
let mut event = DamageEvent {
|
||||
target,
|
||||
damage: None,
|
||||
real_damage: None,
|
||||
weapon: None,
|
||||
};
|
||||
for (key, value) in ParamIter::new(input) {
|
||||
match key {
|
||||
"damage" => event.damage = NonZeroU32::new(u_int(value)?.1),
|
||||
"realdamage" => event.real_damage = NonZeroU32::new(u_int(value)?.1),
|
||||
"weapon" => event.weapon = Some(value.trim_matches('"')),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(("", event))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct KillEvent<'a> {
|
||||
#[event(unnamed)]
|
||||
#[event(skip_after = 1)]
|
||||
pub target: RawSubject<'a>,
|
||||
#[event(name = "with")]
|
||||
pub weapon: &'a str,
|
||||
pub attacker_position: Option<(i32, i32, i32)>,
|
||||
pub victim_position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn kill_event_parser(input: &str) -> IResult<&str, KillEvent> {
|
||||
let (input, target) = against_subject_parser(input)?;
|
||||
let (input, weapon) = param_parse("with")(&input[1..])?;
|
||||
let mut event = KillEvent {
|
||||
target,
|
||||
weapon,
|
||||
attacker_position: None,
|
||||
victim_position: None,
|
||||
};
|
||||
for (key, value) in ParamIter::new(input) {
|
||||
match key {
|
||||
"attacker_position" => event.attacker_position = Some(position(value)?.1),
|
||||
"victim_position" => event.victim_position = Some(position(value)?.1),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(("", event))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct KillAssistEvent<'a> {
|
||||
pub target: RawSubject<'a>,
|
||||
pub attacker_position: Option<(i32, i32, i32)>,
|
||||
pub victim_position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn kill_assist_event_parser(input: &str) -> IResult<&str, KillAssistEvent> {
|
||||
let (input, target) = param_parse_with("against", against_subject_parser)(input)?;
|
||||
let mut event = KillAssistEvent {
|
||||
target,
|
||||
attacker_position: None,
|
||||
victim_position: None,
|
||||
};
|
||||
for (key, value) in ParamIter::new(input) {
|
||||
match key {
|
||||
"attacker_position" => event.attacker_position = Some(position(value)?.1),
|
||||
"victim_position" => event.victim_position = Some(position(value)?.1),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(("", event))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct SpawnEvent {
|
||||
#[event(name = "as")]
|
||||
pub class: Option<Class>,
|
||||
}
|
||||
|
||||
pub fn spawn_event_parser(input: &str) -> IResult<&str, SpawnEvent> {
|
||||
let (input, class_str) = param_parse("as")(input)?;
|
||||
Ok((
|
||||
input,
|
||||
SpawnEvent {
|
||||
class: class_str.parse().ok(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct RoleChangeEvent {
|
||||
#[event(name = "to")]
|
||||
pub class: Option<Class>,
|
||||
}
|
||||
|
||||
pub fn role_changed_event_parser(input: &str) -> IResult<&str, RoleChangeEvent> {
|
||||
let (input, class_str) = param_parse("to")(input)?;
|
||||
Ok((
|
||||
input,
|
||||
RoleChangeEvent {
|
||||
class: class_str.parse().ok(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ConnectedEvent {
|
||||
#[event(name = "to")]
|
||||
pub address: SocketAddr,
|
||||
}
|
||||
|
||||
pub fn connected_event_parser(input: &str) -> IResult<&str, ConnectedEvent> {
|
||||
let (input, address) = param_parse_with("to", parse_from_str)(input)?;
|
||||
Ok((input, ConnectedEvent { address }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct JoinedTeamEvent {
|
||||
pub team: Team,
|
||||
}
|
||||
|
||||
pub fn joined_team_event_parser(input: &str) -> IResult<&str, JoinedTeamEvent> {
|
||||
let (input, team) = param_parse_with("team", parse_from_str)(input)?;
|
||||
Ok((input, JoinedTeamEvent { team }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct CommittedSuicideEvent<'a> {
|
||||
#[event(name = "with")]
|
||||
pub weapon: &'a str,
|
||||
pub attacker_position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn committed_suicide_event_parser(input: &str) -> IResult<&str, CommittedSuicideEvent> {
|
||||
let (input, weapon) = param_parse("with")(input)?;
|
||||
let (input, attacker_position) = opt(param_parse_with("attacker_position", position))(input)?;
|
||||
Ok((
|
||||
input,
|
||||
CommittedSuicideEvent {
|
||||
weapon,
|
||||
attacker_position,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct PickedUpEvent<'a> {
|
||||
pub item: &'a str,
|
||||
}
|
||||
|
||||
pub fn picked_up_event_parser(input: &str) -> IResult<&str, PickedUpEvent> {
|
||||
let (input, item) = param_parse("item")(input)?;
|
||||
Ok((input, PickedUpEvent { item }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct DominationEvent<'a> {
|
||||
pub against: RawSubject<'a>,
|
||||
}
|
||||
|
||||
pub fn domination_event_parser(input: &str) -> IResult<&str, DominationEvent> {
|
||||
let (input, against) = param_parse_with("against", against_subject_parser)(input)?;
|
||||
Ok((input, DominationEvent { against }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct RevengeEvent<'a> {
|
||||
pub against: RawSubject<'a>,
|
||||
}
|
||||
|
||||
pub fn revenge_event_parser(input: &str) -> IResult<&str, RevengeEvent> {
|
||||
let (input, against) = param_parse_with("against", against_subject_parser)(input)?;
|
||||
Ok((input, RevengeEvent { against }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct DisconnectEvent<'a> {
|
||||
pub reason: Option<&'a str>,
|
||||
}
|
||||
|
||||
pub fn disconnected_event_parser(input: &str) -> IResult<&str, DisconnectEvent> {
|
||||
let (input, reason) = opt(param_parse("reason"))(input)?;
|
||||
Ok((input, DisconnectEvent { reason }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct BuiltObjectEvent<'a> {
|
||||
pub object: Option<&'a str>,
|
||||
pub position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn built_object_event_parser(input: &str) -> IResult<&str, BuiltObjectEvent> {
|
||||
let (input, object) = opt(param_parse("object"))(input)?;
|
||||
let (input, position) = opt(param_parse_with("position", position))(input)?;
|
||||
Ok((input, BuiltObjectEvent { object, position }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct KilledObjectEvent<'a> {
|
||||
pub object: Option<&'a str>,
|
||||
pub weapon: Option<&'a str>,
|
||||
#[event(name = "objectowner")]
|
||||
pub object_owner: Option<RawSubject<'a>>,
|
||||
pub attacker_position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn killed_object_event_parser(input: &str) -> IResult<&str, KilledObjectEvent> {
|
||||
let (input, object) = opt(param_parse("object"))(input)?;
|
||||
let (input, weapon) = opt(param_parse("weapon"))(input)?;
|
||||
let (input, object_owner) =
|
||||
opt(param_parse_with("objectowner", against_subject_parser))(input)?;
|
||||
let (input, attacker_position) = opt(param_parse_with("attacker_position", position))(input)?;
|
||||
Ok((
|
||||
input,
|
||||
KilledObjectEvent {
|
||||
object,
|
||||
weapon,
|
||||
object_owner,
|
||||
attacker_position,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ObjectDetonatedEvent<'a> {
|
||||
pub object: Option<&'a str>,
|
||||
#[event(name = "attacker_position")]
|
||||
pub position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn object_detonated_event_parser(input: &str) -> IResult<&str, ObjectDetonatedEvent> {
|
||||
let (input, object) = opt(param_parse("object"))(input)?;
|
||||
let (input, position) = opt(param_parse_with("attacker_position", position))(input)?;
|
||||
Ok((input, ObjectDetonatedEvent { object, position }))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ExtinguishedEvent<'a> {
|
||||
pub against: RawSubject<'a>,
|
||||
pub with: &'a str,
|
||||
pub attacker_position: Option<(i32, i32, i32)>,
|
||||
pub victim_position: Option<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
pub fn extinguished_event_parser(input: &str) -> IResult<&str, ExtinguishedEvent> {
|
||||
let (input, against) = param_parse_with("against", against_subject_parser)(input)?;
|
||||
let (input, with) = param_parse("with")(input)?;
|
||||
let (input, attacker_position) = opt(param_parse_with("attacker_position", position))(input)?;
|
||||
let (input, victim_position) = opt(param_parse_with("victim_position", position))(input)?;
|
||||
Ok((
|
||||
input,
|
||||
ExtinguishedEvent {
|
||||
against,
|
||||
with,
|
||||
attacker_position,
|
||||
victim_position,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
|
|
|||
14
src/lib.rs
14
src/lib.rs
|
|
@ -1,5 +1,3 @@
|
|||
extern crate core;
|
||||
|
||||
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
|
||||
use crate::event::GameEventError;
|
||||
pub use crate::module::EventHandler;
|
||||
|
|
@ -8,13 +6,14 @@ use crate::module::{
|
|||
};
|
||||
pub use crate::subjectmap::SubjectMap;
|
||||
use chrono::NaiveDateTime;
|
||||
pub use event::{EventMeta, GameEvent};
|
||||
pub use event::{Event, EventMeta, GameEvent};
|
||||
use memchr::memmem::{find_iter, FindIter};
|
||||
use nom::Err;
|
||||
pub use raw_event::{RawEvent, RawEventType};
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Debug;
|
||||
pub use tf_log_parser_derive::Event;
|
||||
use thiserror::Error;
|
||||
|
||||
mod common;
|
||||
|
|
@ -53,6 +52,9 @@ impl From<nom::error::Error<&'_ str>> for Error {
|
|||
|
||||
type Result<O, E = Error> = std::result::Result<O, E>;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub type IResult<'a, O> = nom::IResult<&'a str, O>;
|
||||
|
||||
pub fn parse(
|
||||
log: &str,
|
||||
) -> Result<
|
||||
|
|
@ -74,9 +76,7 @@ pub fn parse_with_handler<Handler: EventHandler>(
|
|||
),
|
||||
Error,
|
||||
> {
|
||||
let events = LineSplit::new(log)
|
||||
.filter(|line| !line.is_empty())
|
||||
.map(RawEvent::parse);
|
||||
let events = LineSplit::new(log).map(RawEvent::parse);
|
||||
|
||||
let mut handler = Handler::default();
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ impl<'a> Iterator for LineSplit<'a> {
|
|||
None if self.start < self.input.len() => {
|
||||
let line = &self.input[self.start..];
|
||||
self.start = self.input.len();
|
||||
Some(line.trim_end_matches("\n"))
|
||||
Some(dbg!(line.trim_end_matches("\n")))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ pub struct RawEvent<'a> {
|
|||
|
||||
impl<'a> RawEvent<'a> {
|
||||
pub fn parse(line: &'a str) -> Result<Self> {
|
||||
debug_assert!(!line.ends_with("\n"));
|
||||
event_parser(line)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue