1
0
Fork 0
mirror of https://codeberg.org/demostf/parser.git synced 2026-06-03 18:24:05 +02:00

do codegen with new rust parser

This commit is contained in:
Robin Appelman 2019-08-25 15:02:32 +02:00
commit 8ebee0b0d7
5 changed files with 1327 additions and 0 deletions

364
codegen/src/gameevent.rs Normal file
View file

@ -0,0 +1,364 @@
extern crate proc_macro;
use tf_demo_parser::demo::gamevent::{GameEventDefinition, GameEventValueType};
use tf_demo_parser::demo::message::Message;
use tf_demo_parser::demo::packet::stringtable::StringTableEntry;
use tf_demo_parser::demo::parser::MessageHandler;
use tf_demo_parser::{Demo, ParserState};
use tf_demo_parser::{DemoParser, MessageType};
use inflector::Inflector;
use lazy_static::lazy_static;
use quote::quote;
use proc_macro2::{Span, Ident, TokenStream, Literal};
struct GameEventAnalyser;
impl MessageHandler for GameEventAnalyser {
type Output = Vec<GameEventDefinition>;
fn does_handle(_message_type: MessageType) -> bool {
false
}
fn handle_message(&mut self, _message: Message, _tick: u32) {}
fn handle_string_entry(&mut self, _table: &String, _index: usize, _entry: &StringTableEntry) {}
fn get_output(self, state: ParserState) -> Self::Output {
state
.event_definitions
.into_iter()
.map(|(_, definition)| definition)
.collect()
}
}
fn get_type_name(ty: GameEventValueType) -> &'static str {
match ty {
GameEventValueType::String => "String",
GameEventValueType::Float => "f32",
GameEventValueType::Boolean => "bool",
GameEventValueType::Byte => "u8",
GameEventValueType::Local => "()",
GameEventValueType::Long => "u32",
GameEventValueType::Short => "u16",
GameEventValueType::None => "()",
}
}
fn get_entry_name(name: &str) -> String {
lazy_static! {
static ref REPLACEMENTS: Vec<(&'static str, &'static str)> = vec!(
("mapname", "map_name"),
("cvarname", "cvar_name"),
("cvarvalue", "cvar_value"),
("userid", "user_id"),
("networkid", "network_id"),
("teamid", "team_id"),
("teamname", "team_name"),
("oldteam", "old_team"),
("autoteam", "auto_team"),
("entindex", "ent_index"),
("weaponid", "weapon_id"),
("damagebit", "damage_bit"),
("customkill", "custom_kill"),
("logclassname", "log_class_name"),
("playerpenetratecount", "player_penetrate_count"),
("damageamount", "damage_amount"),
("showdisguisedcrit", "show_disguised_crit"),
("minicrit", "mini_crit"),
("allseecrit", "all_see_crit"),
("weaponid", "weapon_id"),
("bonuseffect", "bonus_effect"),
("teamonly", "team_only"),
("oldname", "old_name"),
("newname", "new_name"),
("hintmessage", "hint_message"),
("roundslimit", "rounds_limit"),
("timelimit", "time_limit"),
("fraglimit", "frag_limit"),
("numadvanced", "num_advanced"),
("numbronze", "num_bronze"),
("numsilver", "num_silver"),
("numgold", "num_gold"),
("oldmode", "old_mode"),
("newmode", "new_mode"),
("entityid", "entity_id"),
("winreason", "win_reason"),
("flagcaplimit", "flag_cap_limit"),
("cpname", "cp_name"),
("capteam", "cap_team"),
("captime", "cap_time"),
("eventtype", "event_type"),
("killstreak", "kill_stream"),
("forceupload", "force_upload"),
("targetid", "target_id"),
("isbuilder", "is_builder"),
("objecttype", "object_type"),
("namechange", "name_change"),
("readystate", "ready_state"),
("builderid", "builder_id"),
("recedetime", "recede_time"),
("ownerid", "owner_id"),
("sapperid", "sapper_id"),
("itemdef", "item_def"),
("bitfield", "bit_field"),
("playsound", "play_sound"),
("totalhits", "total_hits"),
("posx", "pos_x"),
("posy", "pos_y"),
("posz", "pos_z"),
("ineye", "in_eye"),
("maxplayers", "max_players"),
("levelname", "level_name"),
);
}
if name == "type" {
return "kind".to_string();
}
let mut snake: String = name.to_snake_case();
for (search, replace) in REPLACEMENTS.iter() {
snake = snake.replace(search, replace);
}
snake
}
fn get_event_name(name: &str) -> String {
lazy_static! {
static ref REPLACEMENTS: Vec<(&'static str, &'static str)> = vec!(
("ReplayReplaysavailable", "ReplayReplaysAvailable"),
("ServerAddban", "ServerAddBan"),
("ServerRemoveban", "ServerRemoveBan"),
("ClientBeginconnect", "ClientBeginConnect"),
("ClientFullconnect", "ClientFullConnect"),
("PlayerChangename", "PlayerChangeName"),
("PlayerHintmessage", "PlayerHintMessage"),
("GameNewmap", "GameNewMap"),
("IntroNextcamera", "IntroNextCamera"),
("PlayerChangeclass", "PlayerChangeClass"),
("Updateimages", "UpdateImages"),
("Updatelayout", "UpdateLayout"),
("Updatecapping", "UpdateCapping"),
("Updateowner", "UpdateOwner"),
("Starttouch", "StartTouch"),
("Endtouch", "EndTouch"),
("FakeCaptureMult", "FakeCaptureMultiplier"),
("TeamplayWaitingAbouttoend", "TeamPlayWaitingAboutToEnd"),
("TeamplayPointStartcapture", "TeamPlayPointStartCapture"),
("FreezecamStarted", "FreezeCamStarted"),
("LocalplayerChangeteam", "LocalPlayerChangeTeam"),
("LocalplayerChangeclass", "LocalPlayerChangeClass"),
("LocalplayerChangedisguise", "LocalPlayerChangeDisguise"),
("FlagstatusUpdate", "FlagStatusUpdate"),
("TournamentEnablecountdown", "TournamentEnableCountdown"),
("PlayerCalledformedic", "PlayerCalledForMedic"),
("PlayerAskedforball", "PlayerAskedForBall"),
("LocalplayerBecameobserver", "LocalPlayerBecameObserver"),
("PlayerHealedmediccall", "PlayerHealedMedicCall"),
("ArenaMatchMaxstreak", "ArenaMatchMaxStreak"),
("StatsResetround", "StatsResetRound"),
("FishNotice_arm", "FishNoticeArm"),
("PlayerBonuspoints", "PlayerBonusPoints"),
("PlayerUsedPowerupBottle", "PlayerUsedPowerUpBottle"),
("ReplayStartrecord", "ReplayStartRecord"),
("ReplaySessioninfo", "ReplaySessionInfo"),
("ReplayEndrecord", "ReplayEndRecord"),
("ReplayServererror", "ReplayServerError"),
("Teamplay", "TeamPlay"),
("death", "Death"),
("panel", "Panel"),
("object", "Object"),
("update", "Update"),
("ready", "Ready"),
("Gameui", "GameUI"),
("onhit", "OnHit"),
("bymedic", "ByMedic"),
("Controlpoint", "ControlPoint"),
("Pipebomb", "PipeBomb"),
("Scorestats", "ScoreStats"),
("Creditbonus", "CreditBonus"),
("Sentrybuster", "SentryBuster"),
("Questlog", "QuestLog"),
("Localplayer", "LocalPlayer"),
("Minigame", "MiniGame"),
("Winlimit", "WinLimit"),
("Skillrating", "SkillRating"),
("Directhit", "DirectHit"),
("Chargedeployed", "ChargeDeployed"),
("Winddown", "WindDown"),
("Stealsandvich", "StealSandvich"),
("Pricesheet", "PriceSheet"),
("Teambalanced", "TeamBalanced"),
("Highfive", "HighFive"),
("Powerup", "PowerUp"),
("Hltv", "HLTV"),
("Changelevel", "ChangeLevel"),
);
}
let mut snake: String = name.to_pascal_case();
for (search, replace) in REPLACEMENTS.iter() {
snake = snake.replace(search, replace);
}
snake
}
pub fn generate_game_events(demo: Demo) -> TokenStream {
let (_, mut events) =
DemoParser::parse_with_analyser(demo.get_stream(), GameEventAnalyser).unwrap();
events.sort();
let span = Span::call_site();
let imports = quote!(
use super::gamevent::{FromGameEventValue, FromRawGameEvent, GameEventValue, RawGameEvent};
use crate::{GameEventError, MalformedDemoError, Result};
);
let event_definitions = events.iter().map(|event| {
let fields = event.entries.iter().map(|entry| {
let name = Ident::new(&get_entry_name(&entry.name), span);
let ty = Ident::new(get_type_name(entry.kind), span);
quote!(pub #name: #ty,)
});
let name = Ident::new(&format!("{}Event", get_event_name(&event.name)), span);
let item_count = Literal::usize_unsuffixed(event.entries.len());
let entry_constructors = event.entries.iter().rev().map(|entry| {
let name_str = get_entry_name(&entry.name);
let name = Ident::new(&name_str, span);
let ty = Ident::new(get_type_name(entry.kind), span);
quote!(
let #name: #ty = #ty::from_value(
values.pop().ok_or(MalformedDemoError::MalformedGameEvent(
GameEventError::IncorrectValueCount,
))?,
#name_str,
)?;
)
});
let field_names = event.entries.iter().map(|entry| {
let name = Ident::new(&get_entry_name(&entry.name), span);
quote!(#name,)
});
let length_check = if event.entries.len() > 0 {
quote!(
if values.len() < #item_count {
return Err(MalformedDemoError::MalformedGameEvent(
GameEventError::IncorrectValueCount,
)
.into());
}
values.truncate(#item_count);
)
} else {
quote!()
};
let param_name = if event.entries.len() > 0 {
quote!(values)
} else {
quote!(_values)
};
quote!(
#[derive(Debug)]
pub struct #name {
#(#fields)*
}
impl FromRawGameEvent for #name {
fn from_raw_event(mut #param_name: Vec<GameEventValue>) -> Result<Self> {
#length_check
#(#entry_constructors)*
Ok(#name {
#(#field_names)*
})
}
}
)
});
let event_variants = events.iter().map(|event| {
let name_str = get_event_name(&event.name);
let name = Ident::new(&name_str, span);
let struct_name = Ident::new(&format!("{}Event", name_str), span);
quote!(#name(#struct_name),)
});
let event_types = events.iter().map(|event| {
let name_str = get_event_name(&event.name);
let name = Ident::new(&name_str, span);
let id = Literal::u16_unsuffixed(event.id.into());
quote!(#name = #id,)
});
let type_from_names = events.iter().map(|event| {
let name_str = &event.name;
let variant_name = Ident::new(&get_event_name(&name_str), span);
quote!(#name_str => GameEventType::#variant_name,)
});
let from_raw_events = events.iter().map(|event| {
let name = get_event_name(&event.name);
let variant_name = Ident::new(&name, span);
let struct_name = Ident::new(&format!("{}Event", name), span);
quote!(
GameEventType::#variant_name => {
GameEvent::#variant_name(#struct_name::from_raw_event(event.values)?)
}
)
});
quote!(
#imports
#(#event_definitions)*
#[derive(Debug)]
pub enum GameEvent {
#(#event_variants)*
Unknown(RawGameEvent),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum GameEventType {
#(#event_types)*
Unknown,
}
impl GameEventType {
pub fn from_type_name(name: &str) -> Self {
match name {
#(#type_from_names)*
_ => GameEventType::Unknown,
}
}
}
impl GameEvent {
pub fn from_raw_event(event: RawGameEvent) -> Result<Self> {
Ok(match event.event_type {
#(#from_raw_events)*
GameEventType::Unknown => GameEvent::Unknown(event),
})
}
}
)
}

58
codegen/src/main.rs Normal file
View file

@ -0,0 +1,58 @@
use crate::gameevent::generate_game_events;
use std::env;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::process::Command;
use tf_demo_parser::Demo;
mod gameevent;
fn which_rustfmt() -> Option<PathBuf> {
match env::var_os("RUSTFMT") {
Some(which) => {
if which.is_empty() {
None
} else {
Some(PathBuf::from(which))
}
}
None => toolchain_find::find_installed_component("rustfmt"),
}
}
fn format(code: &str) -> io::Result<String> {
let mut builder = tempfile::Builder::new();
builder.prefix("rustfmtr");
let outdir = builder.tempdir()?;
let outfile_path = outdir.path().join("code");
fs::write(&outfile_path, code)?;
let rustfmt = which_rustfmt().ok_or(io::Error::from(io::ErrorKind::NotFound))?;
let _status = Command::new(rustfmt)
.arg("--edition=2018")
.arg(&outfile_path)
.status();
fs::read_to_string(&outfile_path)
}
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
better_panic::install();
let args: Vec<_> = env::args().collect();
if args.len() < 2 {
println!("1 argument required");
return Ok(());
}
let path = args[1].clone();
let file = fs::read(path).expect("Unable to read file");
let demo = Demo::new(file);
let tokens = generate_game_events(demo);
let code = tokens.to_string();
let formatted = format(&code)?;
println!("{}", formatted);
Ok(())
}