From 3928966fd7c0ca4146ac22d8cd13e63c9d6597f8 Mon Sep 17 00:00:00 2001 From: paninizer <101371138+paninizer@users.noreply.github.com> Date: Wed, 24 Apr 2024 20:11:18 -0400 Subject: [PATCH 1/8] Better tracing and interval Title --- Cargo.toml | 2 +- src/commands/owner.rs | 8 ++++++++ src/utilities/event_handler.rs | 15 ++++++++++----- src/utilities/types.rs | 11 +++++++++++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d149029..0330886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,4 +41,4 @@ rand = "0.8" opt-level = 3 [build] -rustflags = ["-Z", "threads=8", "target-cpu=native"] +rustflags = ["-Z", "threads=8", "-C", "target-cpu=native"] diff --git a/src/commands/owner.rs b/src/commands/owner.rs index c272e1e..bef4c1b 100644 --- a/src/commands/owner.rs +++ b/src/commands/owner.rs @@ -1,8 +1,16 @@ use crate::{Context, Error}; use poise; +/// Shuts down the bot gracefully #[poise::command(prefix_command, owners_only, hide_in_help)] pub async fn shutdown(ctx: Context<'_>) -> Result<(), Error> { ctx.framework().shard_manager().shutdown_all().await; Ok(()) } + +/// Utility for global application commands for owner of bot +#[poise::command(prefix_command, owners_only, hide_in_help)] +pub async fn register(ctx: Context<'_>) -> Result<(), Error> { + poise::builtins::register_application_commands_buttons(ctx).await.unwrap(); + Ok(()) +} diff --git a/src/utilities/event_handler.rs b/src/utilities/event_handler.rs index 54166d0..e1e5492 100644 --- a/src/utilities/event_handler.rs +++ b/src/utilities/event_handler.rs @@ -1,8 +1,9 @@ use poise::serenity_prelude as serenity; use serenity::{ActivityData, CreateAllowedMentions}; +use tokio::time; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; -use tracing::{info, error}; +use tracing::{debug, error, info}; use crate::utilities::types::GuildSettings; use crate::{Data, Error}; @@ -66,7 +67,10 @@ pub async fn event_handler( // trim the end to make it easier for mobile users let content = new_message.content.trim_end(); + // calls bot by its mention only, let's respond with an embed telling them what the prefix for help command is if content == "<@!1183487567094632638>" || content == "<@1183487567094632638>" { + debug!("Received mention from {}", new_message.author.id); + let prefix = { let guild_data = &data.guild_data; let pf = guild_data; @@ -118,13 +122,14 @@ pub async fn event_handler( // And of course, we can run more than one thread at different timings.' let guild_len = guilds.len(); let cloned = context.clone(); + let mut interval = time::interval(Duration::from_secs(3)); tokio::spawn(async move { loop { set_activity(&cloned, guild_len); - tokio::time::sleep(Duration::from_secs(3)).await; + interval.tick().await; set_ad(&cloned); - tokio::time::sleep(Duration::from_secs(3)).await; + interval.tick().await; } }); @@ -158,7 +163,7 @@ pub async fn event_handler( .await .unwrap(); - info!("Owner Query: {owner_query:?}"); + debug!("Owner Query: {owner_query:?}"); let query = sqlx::query!( "INSERT INTO guild ( @@ -178,7 +183,7 @@ pub async fn event_handler( .await .unwrap(); - info!("Guild Settings Query: {query:?}"); + debug!("Guild Settings Query: {query:?}"); let fetched_guild = sqlx::query!("SELECT * FROM guild WHERE id = ?", guild_id,) .fetch_one(&database) diff --git a/src/utilities/types.rs b/src/utilities/types.rs index b9e5faa..fab1daa 100644 --- a/src/utilities/types.rs +++ b/src/utilities/types.rs @@ -1,5 +1,7 @@ use serde::Deserialize; +// Guild settings type below + #[derive(Debug, Clone)] pub struct GuildSettings { pub prefix: String, @@ -9,12 +11,16 @@ pub struct GuildSettings { pub default_mute_duration: u64, } +// Guild stat type below + #[derive(Debug, Clone)] pub struct GuildStats { pub commands_ran: u64, pub songs_played: u64, } +// Wish type below + #[derive(Debug, Clone)] pub struct User { pub id: u64, @@ -26,6 +32,8 @@ pub struct User { pub character_pity: u64, } +// Neko API needed deseralization types below + #[derive(Deserialize, Clone)] pub struct Item { pub image_url: String, @@ -37,6 +45,9 @@ pub struct Items { pub items: Vec, } + +// Wikipedia API needed deserialization types below + #[derive(Deserialize)] pub struct WikiQuery( pub String, From 1e979d5e3b93531a14735e9b3bb6610de153a2ae Mon Sep 17 00:00:00 2001 From: paninizer <101371138+paninizer@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:14:53 -0400 Subject: [PATCH 2/8] clippy fixes and more tracing --- src/commands/wiki.rs | 75 +++++++++++++++------------------- src/utilities/command.rs | 14 ++++++- src/utilities/event_handler.rs | 1 + 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/commands/wiki.rs b/src/commands/wiki.rs index 4fb2d63..7cb829a 100644 --- a/src/commands/wiki.rs +++ b/src/commands/wiki.rs @@ -58,7 +58,7 @@ pub async fn wiki( let ctx_id = ctx.id(); let menu = CreateSelectMenu::new( - &ctx_id.to_string(), + ctx_id.to_string(), poise::serenity_prelude::CreateSelectMenuKind::String { options }, ) .max_values(1) @@ -87,52 +87,45 @@ pub async fn wiki( // TODO: Implement select interaction to show the summary of the selected article interaction.defer(ctx.http()).await?; - match interaction.data.kind { - poise::serenity_prelude::ComponentInteractionDataKind::StringSelect { - values, - } => { - if let Some(value) = values.first() { - // interaction.channel_id.say(ctx.http(), "https://en.wikipedia.org/wiki/".to_owned() + value).await?; // debug - - //TODO: Add summary fetching - let request = &ctx.data().reqwest; - let url = format!("https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles={value}"); - - let get = request.get(url).send().await; - - let res = match get { - Ok(res) => res.text().await, - Err(_) => { - return Err("Failed to get data.".into()); - } - }; - - let res = res.unwrap(); - - let data: QueryContainer = serde_json::from_str(&res).unwrap(); - //info!("{:?}", data); - - let data = data.query; - - let embed = CreateEmbed::new() - .title(data.pages.title) - .description(data.pages.extract); - - let message = - CreateMessage::new().embed(embed).reference_message(&refer); - - ctx.channel_id().send_message(ctx.http(), message).await?; - } + if let poise::serenity_prelude::ComponentInteractionDataKind::StringSelect { + values, + } = interaction.data.kind + { + if let Some(value) = values.first() { + // interaction.channel_id.say(ctx.http(), "https://en.wikipedia.org/wiki/".to_owned() + value).await?; // debug + //TODO: Add summary fetching + let request = &ctx.data().reqwest; + let url = format!("https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles={value}"); + + let get = request.get(url).send().await; + + let res = match get { + Ok(res) => res.text().await, + Err(_) => { + return Err("Failed to get data.".into()); + } + }; + let res = res.unwrap(); + let data: QueryContainer = serde_json::from_str(&res).unwrap(); + //info!("{:?}", data); + + let data = data.query; + let embed = CreateEmbed::new() + .title(data.pages.title) + .description(data.pages.extract); + + let message = CreateMessage::new().embed(embed).reference_message(&refer); + + ctx.channel_id().send_message(ctx.http(), message).await?; } - _ => {} } } - return Ok(()); + Ok(()) } else { - return Err("Failed to deserialize the data from the Wikipedia API.".into()); + Err("Failed to deserialize the data from the Wikipedia API.".into()) } } else { - return Err("Wikipedia API data request failed.".into()); + Err("Wikipedia API data request failed.".into()) } } diff --git a/src/utilities/command.rs b/src/utilities/command.rs index a760876..358d396 100644 --- a/src/utilities/command.rs +++ b/src/utilities/command.rs @@ -1,7 +1,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; use sqlx::sqlite::SqliteQueryResult; -use tracing::error; +use tokio::time::Instant; +use tracing::{debug, error}; use crate::{Context, Error, PartialContext}; @@ -66,7 +67,12 @@ pub async fn get_prefix(context: PartialContext<'_>) -> Result, E } } -pub async fn pre_command(context: Context<'_>) -> () { +pub async fn pre_command(context: Context<'_>) { + + debug!("{} (UID: {}) ran {}", context.author().name, context.author().id, context.command().name); + + let start_time = Instant::now(); + if let Some(guild_id) = context.guild_id() { let commands_ran = context.data().commands_ran.get(&guild_id.get()).unwrap(); commands_ran.fetch_add(1, Ordering::Relaxed); @@ -131,4 +137,8 @@ pub async fn pre_command(context: Context<'_>) -> () { { error!("Failed to insert user: {}", query); } + + let elapsed_time = start_time.elapsed(); + + debug!("Precommand ran in {elapsed_time:.2?}"); } diff --git a/src/utilities/event_handler.rs b/src/utilities/event_handler.rs index e1e5492..79d74f3 100644 --- a/src/utilities/event_handler.rs +++ b/src/utilities/event_handler.rs @@ -3,6 +3,7 @@ use serenity::{ActivityData, CreateAllowedMentions}; use tokio::time; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; + use tracing::{debug, error, info}; use crate::utilities::types::GuildSettings; From 893aafc33194b0f6f5a6fbef9ecbac0cec4062fc Mon Sep 17 00:00:00 2001 From: paninizer <101371138+paninizer@users.noreply.github.com> Date: Fri, 26 Apr 2024 12:35:37 -0400 Subject: [PATCH 3/8] refactor into smaller crates theoretically this will speed up compilation --- Cargo.toml | 26 +++++++++- bismarck_commands/Cargo.toml | 24 +++++++++ .../src}/info.rs | 4 +- .../mod.rs => bismarck_commands/src/lib.rs | 1 - .../src}/math.rs | 2 +- .../src}/moderation.rs | 51 ++++++++----------- .../src}/neko.rs | 2 +- .../src}/owner.rs | 6 ++- .../src}/settings.rs | 0 .../src}/setup.rs | 2 +- .../src}/utilities.rs | 2 +- .../src}/wiki.rs | 5 +- .../src}/wish.rs | 2 +- bismarck_core/Cargo.toml | 14 +++++ bismarck_core/src/context.rs | 6 +++ bismarck_core/src/data.rs | 19 +++++++ bismarck_core/src/error.rs | 7 +++ bismarck_core/src/lib.rs | 5 ++ {src/utilities => bismarck_core/src}/types.rs | 1 - bismarck_events/Cargo.toml | 16 ++++++ .../src}/event_handler.rs | 5 +- bismarck_events/src/lib.rs | 2 + .../src}/on_error.rs | 5 +- bismarck_utilities/Cargo.toml | 19 +++++++ .../src}/command.rs | 10 ++-- .../src}/embeds.rs | 0 .../src}/git.rs | 0 .../mod.rs => bismarck_utilities/src/lib.rs | 4 +- .../src}/messages.rs | 0 .../src}/models.rs | 2 +- .../src}/modlog.rs | 0 .../src}/paginate.rs | 2 +- src/main.rs | 36 +++---------- 33 files changed, 193 insertions(+), 87 deletions(-) create mode 100644 bismarck_commands/Cargo.toml rename {src/commands => bismarck_commands/src}/info.rs (98%) rename src/commands/mod.rs => bismarck_commands/src/lib.rs (88%) rename {src/commands => bismarck_commands/src}/math.rs (97%) rename {src/commands => bismarck_commands/src}/moderation.rs (93%) rename {src/commands => bismarck_commands/src}/neko.rs (95%) rename {src/commands => bismarck_commands/src}/owner.rs (74%) rename {src/commands => bismarck_commands/src}/settings.rs (100%) rename {src/commands => bismarck_commands/src}/setup.rs (98%) rename {src/commands => bismarck_commands/src}/utilities.rs (99%) rename {src/commands => bismarck_commands/src}/wiki.rs (98%) rename {src/commands => bismarck_commands/src}/wish.rs (99%) create mode 100644 bismarck_core/Cargo.toml create mode 100644 bismarck_core/src/context.rs create mode 100644 bismarck_core/src/data.rs create mode 100644 bismarck_core/src/error.rs create mode 100644 bismarck_core/src/lib.rs rename {src/utilities => bismarck_core/src}/types.rs (99%) create mode 100644 bismarck_events/Cargo.toml rename {src/utilities => bismarck_events/src}/event_handler.rs (99%) create mode 100644 bismarck_events/src/lib.rs rename {src/utilities => bismarck_events/src}/on_error.rs (96%) create mode 100644 bismarck_utilities/Cargo.toml rename {src/utilities => bismarck_utilities/src}/command.rs (95%) rename {src/utilities => bismarck_utilities/src}/embeds.rs (100%) rename {src/utilities => bismarck_utilities/src}/git.rs (100%) rename src/utilities/mod.rs => bismarck_utilities/src/lib.rs (67%) rename {src/utilities => bismarck_utilities/src}/messages.rs (100%) rename {src/utilities => bismarck_utilities/src}/models.rs (96%) rename {src/utilities => bismarck_utilities/src}/modlog.rs (100%) rename {src/utilities => bismarck_utilities/src}/paginate.rs (98%) diff --git a/Cargo.toml b/Cargo.toml index 0330886..5c7686f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,33 @@ name = "bismarck" version = "0.0.1" edition = "2021" +[workspace] +members = [ + "bismarck_commands", + "bismarck_core", + "bismarck_events", + "bismarck_utilities" +] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dotenv = { workspace = true } +poise = { workspace = true } +serenity = { workspace = true } +sqlx = { workspace = true } +dashmap = { workspace = true } +reqwest = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } + +bismarck_commands = { path = "bismarck_commands" } +bismarck_core = { path = "bismarck_core" } +bismarck_events = { path = "bismarck_events" } +bismarck_utilities = { path = "bismarck_utilities" } + +[workspace.dependencies] poise = { git = "https://github.com/serenity-rs/poise/", branch = "current", features = ["cache"] } serenity = { version = "^0.12.1", default-features = false, features = ["rustls_backend", "chrono", "gateway"] } serde = "^1.0.197" @@ -32,7 +56,7 @@ reqwest = { version = "0.12.0", features = ["json"] } lazy_static = "1.4.0" dashmap = "^5.5.3" uuid = { version = "1.7.0", features = ["v4"] } -duration-str = "0.8.0" +duration-str = "0.9.1" rand = "0.8" diff --git a/bismarck_commands/Cargo.toml b/bismarck_commands/Cargo.toml new file mode 100644 index 0000000..74ef772 --- /dev/null +++ b/bismarck_commands/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bismarck_commands" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +chrono = { workspace = true } +duration-str = { workspace = true } +poise = { workspace = true } +serenity = { workspace = true } +serde_json = { workspace = true } +sqlx = { workspace = true } +git2 = { workspace = true } +reqwest = { workspace = true } +sysinfo = { workspace = true } +tracing = { workspace = true } + +rand = { workspace = true } + +bismarck_utilities = { path = "../bismarck_utilities" } +bismarck_core = { path = "../bismarck_core" } \ No newline at end of file diff --git a/src/commands/info.rs b/bismarck_commands/src/info.rs similarity index 98% rename from src/commands/info.rs rename to bismarck_commands/src/info.rs index ca6f175..42e6785 100644 --- a/src/commands/info.rs +++ b/bismarck_commands/src/info.rs @@ -1,7 +1,7 @@ use std::sync::atomic::Ordering; -use crate::utilities::git::{get_current_branch, get_head_revision}; -use crate::{Context, Error}; +use bismarck_utilities::git::{get_current_branch, get_head_revision}; +use bismarck_core::{context::Context, error::Error}; use git2::Repository; use poise::{serenity_prelude as serenity, CreateReply}; diff --git a/src/commands/mod.rs b/bismarck_commands/src/lib.rs similarity index 88% rename from src/commands/mod.rs rename to bismarck_commands/src/lib.rs index 82c2f65..8e98249 100644 --- a/src/commands/mod.rs +++ b/bismarck_commands/src/lib.rs @@ -3,7 +3,6 @@ pub mod math; pub mod moderation; pub mod neko; pub mod owner; -pub mod settings; pub mod setup; pub mod utilities; pub mod wiki; diff --git a/src/commands/math.rs b/bismarck_commands/src/math.rs similarity index 97% rename from src/commands/math.rs rename to bismarck_commands/src/math.rs index 63e32fe..e76fe47 100644 --- a/src/commands/math.rs +++ b/bismarck_commands/src/math.rs @@ -1,4 +1,4 @@ -use crate::{Context, Error}; +use bismarck_core::{context::Context, error::Error}; /// Multiplies two numbers. #[poise::command( diff --git a/src/commands/moderation.rs b/bismarck_commands/src/moderation.rs similarity index 93% rename from src/commands/moderation.rs rename to bismarck_commands/src/moderation.rs index 6a2de74..2f401f3 100644 --- a/src/commands/moderation.rs +++ b/bismarck_commands/src/moderation.rs @@ -1,14 +1,7 @@ use std::time::Duration; -use crate::{ - utilities::{ - self, - embeds::warnings_command_embed, - messages, models, - modlog::{self, ensure_user, ModType}, - }, - Context, Error, -}; +use bismarck_core::{context::Context, error::Error}; +use bismarck_utilities::{embeds::warnings_command_embed, messages, models, modlog::*, paginate}; use chrono::{Days, NaiveDateTime, Utc}; use duration_str::parse; @@ -88,7 +81,7 @@ pub async fn ban( let created_at = Utc::now().naive_utc(); - let mut user_mod_history = modlog::select_modlog_from_users(&user_id, database).await?; + let mut user_mod_history = select_modlog_from_users(&user_id, database).await?; let message = messages::info_message(format!( "You've been banned from {guild_name} by {moderator_mention} for {reason}.", @@ -101,7 +94,7 @@ pub async fn ban( match guild_id.ban_with_reason(context, user_id, 0, &reason).await { Ok(_) => { - modlog::insert_modlog( + insert_modlog( ModType::Ban, &guild_id, &user_id, @@ -114,7 +107,7 @@ pub async fn ban( user_mod_history += 1; - modlog::update_users_set_modlog(&user_id, user_mod_history, database).await?; + update_users_set_modlog(&user_id, user_mod_history, database).await?; info!("@{moderator_name} banned @{user_name} from {guild_name}: {reason}"); Ok(format!("{user_mention} has been banned.")) @@ -209,7 +202,7 @@ pub async fn kick( let created_at = Utc::now().naive_utc(); - let mut user_mod_history = modlog::select_modlog_from_users(&user_id, database).await?; + let mut user_mod_history = select_modlog_from_users(&user_id, database).await?; let message = messages::info_message(format!( "You've been kicked from {guild_name} by {moderator_mention} for {reason}.", @@ -222,7 +215,7 @@ pub async fn kick( match guild_id.kick_with_reason(context, user_id, &reason).await { Ok(_) => { - modlog::insert_modlog( + insert_modlog( ModType::Kick, &guild_id, &user_id, @@ -235,7 +228,7 @@ pub async fn kick( user_mod_history += 1; - modlog::update_users_set_modlog(&user_id, user_mod_history, database).await?; + update_users_set_modlog(&user_id, user_mod_history, database).await?; info!("@{moderator_name} kicked @{user_name} from {guild_name}: {reason}"); Ok(format!("{user_mention} has been kicked.")) @@ -330,11 +323,11 @@ pub async fn unban( let created_at = Utc::now().naive_utc(); - let mut user_mod_history = modlog::select_modlog_from_users(&user_id, database).await?; + let mut user_mod_history = select_modlog_from_users(&user_id, database).await?; match guild_id.unban(context, user_id).await { Ok(_) => { - modlog::insert_modlog( + insert_modlog( ModType::Unban, &guild_id, &user_id, @@ -347,7 +340,7 @@ pub async fn unban( user_mod_history += 1; - modlog::update_users_set_modlog(&user_id, user_mod_history, database).await?; + update_users_set_modlog(&user_id, user_mod_history, database).await?; info!("@{moderator_name} unbanned @{user_name} from {guild_name}: {reason}"); Ok(format!("{user_mention} has been unbanned.")) @@ -468,14 +461,14 @@ pub async fn timeout( let created_at = Utc::now().naive_utc(); - let mut user_mod_history = modlog::select_modlog_from_users(&user_id, database).await?; + let mut user_mod_history = select_modlog_from_users(&user_id, database).await?; match member .disable_communication_until_datetime(context, time) .await { Ok(_) => { - modlog::insert_modlog( + insert_modlog( ModType::Timeout, &guild_id, &user_id, @@ -488,7 +481,7 @@ pub async fn timeout( user_mod_history += 1; - modlog::update_users_set_modlog(&user_id, user_mod_history, database).await?; + update_users_set_modlog(&user_id, user_mod_history, database).await?; info!("@{moderator_name} timed out @{user_name} from {guild_name}: {reason}"); Ok(format!("{user_mention} has been timed out.")) @@ -562,11 +555,11 @@ pub async fn untimeout( let created_at = Utc::now().naive_utc(); - let mut user_mod_history = modlog::select_modlog_from_users(&user_id, database).await?; + let mut user_mod_history = select_modlog_from_users(&user_id, database).await?; match member.enable_communication(context).await { Ok(_) => { - modlog::insert_modlog( + insert_modlog( ModType::Untimeout, &guild_id, &user_id, @@ -579,7 +572,7 @@ pub async fn untimeout( user_mod_history += 1; - modlog::update_users_set_modlog(&user_id, user_mod_history, database).await?; + update_users_set_modlog(&user_id, user_mod_history, database).await?; info!("@{moderator_id} untimed out @{user_name} from {guild_name}"); Ok(format!("{user_mention} has been untimed out.")) @@ -652,9 +645,9 @@ pub async fn warn( let created_at = Utc::now().naive_utc(); - let mut user_mod_history = modlog::select_modlog_from_users(&user_id, database).await?; + let mut user_mod_history = select_modlog_from_users(&user_id, database).await?; - match modlog::insert_modlog( + match insert_modlog( ModType::Warn, &guild_id, &user_id, @@ -668,7 +661,7 @@ pub async fn warn( Ok(_) => { user_mod_history += 1; - modlog::update_users_set_modlog(&user_id, user_mod_history, database).await?; + update_users_set_modlog(&user_id, user_mod_history, database).await?; info!("@{moderator_id} warned @{user_name} from {guild_name}"); Ok(format!("{user_mention} has been warned.")) @@ -733,7 +726,7 @@ pub async fn warnings( let guild_id = context.guild_id().unwrap(); let user_mod_history = - match modlog::select_modlog(ModType::Warn, &user_id, &guild_id, database).await { + match select_modlog(ModType::Warn, &user_id, &guild_id, database).await { Ok(user_mod_history) => user_mod_history, Err(why) => { error!("Couldn't select warnings from infractions: {why:?}"); @@ -789,7 +782,7 @@ pub async fn warnings( )); }); - match utilities::paginate::paginate(context, embeds).await { + match paginate::paginate(context, embeds).await { Ok(_) => { let author = context.author().id; info!("@{author} requested @{user_name}'s warnings"); diff --git a/src/commands/neko.rs b/bismarck_commands/src/neko.rs similarity index 95% rename from src/commands/neko.rs rename to bismarck_commands/src/neko.rs index c10d57e..9fd46d0 100644 --- a/src/commands/neko.rs +++ b/bismarck_commands/src/neko.rs @@ -1,7 +1,7 @@ use poise::CreateReply; use serenity::all::{CreateEmbed, CreateEmbedFooter}; -use crate::{utilities::types::Items, Context, Error}; +use bismarck_core::{context::Context, error::Error, types::Items}; /// Sends a random Neko image. #[poise::command( diff --git a/src/commands/owner.rs b/bismarck_commands/src/owner.rs similarity index 74% rename from src/commands/owner.rs rename to bismarck_commands/src/owner.rs index bef4c1b..4ef30c4 100644 --- a/src/commands/owner.rs +++ b/bismarck_commands/src/owner.rs @@ -1,4 +1,4 @@ -use crate::{Context, Error}; +use bismarck_core::{context::Context, error::Error}; use poise; /// Shuts down the bot gracefully @@ -11,6 +11,8 @@ pub async fn shutdown(ctx: Context<'_>) -> Result<(), Error> { /// Utility for global application commands for owner of bot #[poise::command(prefix_command, owners_only, hide_in_help)] pub async fn register(ctx: Context<'_>) -> Result<(), Error> { - poise::builtins::register_application_commands_buttons(ctx).await.unwrap(); + poise::builtins::register_application_commands_buttons(ctx) + .await + .unwrap(); Ok(()) } diff --git a/src/commands/settings.rs b/bismarck_commands/src/settings.rs similarity index 100% rename from src/commands/settings.rs rename to bismarck_commands/src/settings.rs diff --git a/src/commands/setup.rs b/bismarck_commands/src/setup.rs similarity index 98% rename from src/commands/setup.rs rename to bismarck_commands/src/setup.rs index b63e035..566f4ee 100644 --- a/src/commands/setup.rs +++ b/bismarck_commands/src/setup.rs @@ -1,4 +1,4 @@ -use crate::{utilities::types::GuildSettings, Context, Error}; +use bismarck_core::{context::Context, error::Error, types::GuildSettings}; use poise::{serenity_prelude as serenity, CreateReply}; use serenity::{CreateEmbed, CreateEmbedFooter}; use tracing::info; diff --git a/src/commands/utilities.rs b/bismarck_commands/src/utilities.rs similarity index 99% rename from src/commands/utilities.rs rename to bismarck_commands/src/utilities.rs index a8724b2..bd0bbf7 100644 --- a/src/commands/utilities.rs +++ b/bismarck_commands/src/utilities.rs @@ -1,4 +1,4 @@ -use crate::{Context, Error}; +use bismarck_core::{context::Context, error::Error}; use chrono::Duration; use chrono::Utc; use poise::builtins::PrettyHelpConfiguration; diff --git a/src/commands/wiki.rs b/bismarck_commands/src/wiki.rs similarity index 98% rename from src/commands/wiki.rs rename to bismarck_commands/src/wiki.rs index 7cb829a..45c01ea 100644 --- a/src/commands/wiki.rs +++ b/bismarck_commands/src/wiki.rs @@ -1,10 +1,7 @@ use poise::CreateReply; use serenity::all::{CreateEmbed, CreateMessage, CreateSelectMenu, CreateSelectMenuOption}; -use crate::{ - utilities::types::{QueryContainer, WikiQuery}, - Context, Error, -}; +use bismarck_core::{context::Context, error::Error, types::{WikiQuery, QueryContainer}}; /// Shows Wikipedia search results. #[poise::command( diff --git a/src/commands/wish.rs b/bismarck_commands/src/wish.rs similarity index 99% rename from src/commands/wish.rs rename to bismarck_commands/src/wish.rs index 384a1cf..8c8b247 100644 --- a/src/commands/wish.rs +++ b/bismarck_commands/src/wish.rs @@ -5,7 +5,7 @@ mod wish_tests { use super::*; const ROLLS: u32 = 1_000_000; - const EPS: f64 = 1./300.; + const EPS: f64 = 1. / 300.; fn test_tol(result: f64, expected: f64) { let res = result / ROLLS as f64; diff --git a/bismarck_core/Cargo.toml b/bismarck_core/Cargo.toml new file mode 100644 index 0000000..31b51dd --- /dev/null +++ b/bismarck_core/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "bismarck_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +poise = { workspace = true } +serenity = { workspace = true } +sqlx = { workspace = true } +dashmap = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } diff --git a/bismarck_core/src/context.rs b/bismarck_core/src/context.rs new file mode 100644 index 0000000..1598e91 --- /dev/null +++ b/bismarck_core/src/context.rs @@ -0,0 +1,6 @@ +use poise; +use crate::data::Data; +use crate::error::Error; + +pub type Context<'a> = poise::Context<'a, Data, Error>; +pub type PartialContext<'a> = poise::PartialContext<'a, Data, Error>; \ No newline at end of file diff --git a/bismarck_core/src/data.rs b/bismarck_core/src/data.rs new file mode 100644 index 0000000..00af65e --- /dev/null +++ b/bismarck_core/src/data.rs @@ -0,0 +1,19 @@ +use dashmap::DashMap; +use poise::serenity_prelude as serenity; +use std::sync::atomic::{AtomicBool, AtomicU64}; +use std::sync::Arc; +use sqlx::SqlitePool; +use crate::types::{GuildSettings, User}; + +#[derive(Debug)] +pub struct Data { + pub reqwest: reqwest::Client, + pub sqlite: SqlitePool, + pub guild_data: DashMap, + pub users: DashMap, + pub commands_ran: DashMap, + pub commands_ran_users: DashMap, + pub songs_played: DashMap, + pub shard_manager: Arc, + pub is_loop_running: AtomicBool, +} // User data, which is stored and accessible in all command invocations \ No newline at end of file diff --git a/bismarck_core/src/error.rs b/bismarck_core/src/error.rs new file mode 100644 index 0000000..51e2815 --- /dev/null +++ b/bismarck_core/src/error.rs @@ -0,0 +1,7 @@ +use poise; +use data::Data; + +use crate::data; + +pub type Error = Box; +pub type FrameworkError<'a> = poise::FrameworkError<'a, Data, Error>; \ No newline at end of file diff --git a/bismarck_core/src/lib.rs b/bismarck_core/src/lib.rs new file mode 100644 index 0000000..eefc42f --- /dev/null +++ b/bismarck_core/src/lib.rs @@ -0,0 +1,5 @@ + +pub mod context; +pub mod data; +pub mod error; +pub mod types; \ No newline at end of file diff --git a/src/utilities/types.rs b/bismarck_core/src/types.rs similarity index 99% rename from src/utilities/types.rs rename to bismarck_core/src/types.rs index fab1daa..e10778d 100644 --- a/src/utilities/types.rs +++ b/bismarck_core/src/types.rs @@ -45,7 +45,6 @@ pub struct Items { pub items: Vec, } - // Wikipedia API needed deserialization types below #[derive(Deserialize)] diff --git a/bismarck_events/Cargo.toml b/bismarck_events/Cargo.toml new file mode 100644 index 0000000..9cd24b0 --- /dev/null +++ b/bismarck_events/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bismarck_events" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +poise = { workspace = true } +serenity = { workspace = true } +sqlx = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } + +bismarck_utilities = { path = "../bismarck_utilities" } +bismarck_core = { path = "../bismarck_core" } diff --git a/src/utilities/event_handler.rs b/bismarck_events/src/event_handler.rs similarity index 99% rename from src/utilities/event_handler.rs rename to bismarck_events/src/event_handler.rs index 79d74f3..32c0a80 100644 --- a/src/utilities/event_handler.rs +++ b/bismarck_events/src/event_handler.rs @@ -1,13 +1,12 @@ use poise::serenity_prelude as serenity; use serenity::{ActivityData, CreateAllowedMentions}; -use tokio::time; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; +use tokio::time; use tracing::{debug, error, info}; -use crate::utilities::types::GuildSettings; -use crate::{Data, Error}; +use bismarck_core::{data::Data, error::Error, types::GuildSettings}; pub async fn event_handler( context: &serenity::Context, diff --git a/bismarck_events/src/lib.rs b/bismarck_events/src/lib.rs new file mode 100644 index 0000000..717d74c --- /dev/null +++ b/bismarck_events/src/lib.rs @@ -0,0 +1,2 @@ +pub mod event_handler; +pub mod on_error; diff --git a/src/utilities/on_error.rs b/bismarck_events/src/on_error.rs similarity index 96% rename from src/utilities/on_error.rs rename to bismarck_events/src/on_error.rs index af0c6d7..b34375a 100644 --- a/src/utilities/on_error.rs +++ b/bismarck_events/src/on_error.rs @@ -1,8 +1,7 @@ use tracing::error; -use crate::FrameworkError; - -use super::messages; +use bismarck_core::error::FrameworkError; +use bismarck_utilities::messages; pub async fn on_error(error: FrameworkError<'_>) { error!("Unhandled error occured: {error:?}"); diff --git a/bismarck_utilities/Cargo.toml b/bismarck_utilities/Cargo.toml new file mode 100644 index 0000000..b5eeac9 --- /dev/null +++ b/bismarck_utilities/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bismarck_utilities" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { workspace = true } +poise = { workspace = true } +serenity = { workspace = true } +sqlx = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +serde = { workspace = true } +git2 = { workspace = true } +uuid = { workspace = true } + +bismarck_core = { path = "../bismarck_core" } \ No newline at end of file diff --git a/src/utilities/command.rs b/bismarck_utilities/src/command.rs similarity index 95% rename from src/utilities/command.rs rename to bismarck_utilities/src/command.rs index 358d396..ac0b0a9 100644 --- a/src/utilities/command.rs +++ b/bismarck_utilities/src/command.rs @@ -4,7 +4,7 @@ use sqlx::sqlite::SqliteQueryResult; use tokio::time::Instant; use tracing::{debug, error}; -use crate::{Context, Error, PartialContext}; +use bismarck_core::{context::{Context, PartialContext}, error::Error}; pub async fn get_prefix(context: PartialContext<'_>) -> Result, Error> { if let Some(guild_id) = context.guild_id { @@ -68,8 +68,12 @@ pub async fn get_prefix(context: PartialContext<'_>) -> Result, E } pub async fn pre_command(context: Context<'_>) { - - debug!("{} (UID: {}) ran {}", context.author().name, context.author().id, context.command().name); + debug!( + "{} (UID: {}) ran {}", + context.author().name, + context.author().id, + context.command().name + ); let start_time = Instant::now(); diff --git a/src/utilities/embeds.rs b/bismarck_utilities/src/embeds.rs similarity index 100% rename from src/utilities/embeds.rs rename to bismarck_utilities/src/embeds.rs diff --git a/src/utilities/git.rs b/bismarck_utilities/src/git.rs similarity index 100% rename from src/utilities/git.rs rename to bismarck_utilities/src/git.rs diff --git a/src/utilities/mod.rs b/bismarck_utilities/src/lib.rs similarity index 67% rename from src/utilities/mod.rs rename to bismarck_utilities/src/lib.rs index 5fa2f6f..0d3eeec 100644 --- a/src/utilities/mod.rs +++ b/bismarck_utilities/src/lib.rs @@ -1,10 +1,8 @@ + pub mod command; pub mod embeds; -pub mod event_handler; pub mod git; pub mod messages; pub mod models; pub mod modlog; -pub mod on_error; pub mod paginate; -pub mod types; diff --git a/src/utilities/messages.rs b/bismarck_utilities/src/messages.rs similarity index 100% rename from src/utilities/messages.rs rename to bismarck_utilities/src/messages.rs diff --git a/src/utilities/models.rs b/bismarck_utilities/src/models.rs similarity index 96% rename from src/utilities/models.rs rename to bismarck_utilities/src/models.rs index 1a8e19c..0e82f72 100644 --- a/src/utilities/models.rs +++ b/bismarck_utilities/src/models.rs @@ -1,4 +1,4 @@ -use crate::Context; +use bismarck_core::context::Context; use poise::serenity_prelude::{model::ModelError, User, UserId}; use serenity::all::{GuildId, Member, Mention, Mentionable}; use tracing::error; diff --git a/src/utilities/modlog.rs b/bismarck_utilities/src/modlog.rs similarity index 100% rename from src/utilities/modlog.rs rename to bismarck_utilities/src/modlog.rs diff --git a/src/utilities/paginate.rs b/bismarck_utilities/src/paginate.rs similarity index 98% rename from src/utilities/paginate.rs rename to bismarck_utilities/src/paginate.rs index 694524a..e374751 100644 --- a/src/utilities/paginate.rs +++ b/bismarck_utilities/src/paginate.rs @@ -1,7 +1,7 @@ use poise::serenity_prelude as serenity; use uuid::Uuid; -use crate::Context; +use bismarck_core::context::Context; /// Paginates a list of embeds using UUID as custom ID to identify the buttons. pub async fn paginate( diff --git a/src/main.rs b/src/main.rs index d5f5f4c..d174f01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use bismarck_core::context::PartialContext; +use bismarck_core::data::Data; use dashmap::DashMap; use poise::serenity_prelude as serenity; use std::env; @@ -5,36 +7,14 @@ use std::sync::atomic::{AtomicBool, AtomicU64}; use std::{sync::Arc, time::Duration}; use tokio::time::sleep; use tracing::{error, info}; -use utilities::event_handler::event_handler; -use utilities::on_error::on_error; -use utilities::types::{GuildSettings, User}; +use bismarck_events::event_handler::event_handler; +use bismarck_events::on_error::on_error; +use bismarck_core::types::{GuildSettings, User}; -mod commands; -mod utilities; - -use crate::commands::{ +use bismarck_commands::{ info::*, math::*, moderation::*, neko::*, owner::*, setup::*, utilities::*, wiki::*, }; -use sqlx::SqlitePool; - -#[derive(Debug)] -pub struct Data { - pub reqwest: reqwest::Client, - pub sqlite: SqlitePool, - pub guild_data: DashMap, - pub users: DashMap, - pub commands_ran: DashMap, - pub commands_ran_users: DashMap, - pub songs_played: DashMap, - pub shard_manager: Arc, - pub is_loop_running: AtomicBool, -} // User data, which is stored and accessible in all command invocations -pub type Error = Box; -pub type FrameworkError<'a> = poise::FrameworkError<'a, Data, Error>; -pub type Context<'a> = poise::Context<'a, Data, Error>; -pub type PartialContext<'a> = poise::PartialContext<'a, Data, Error>; - #[tokio::main] async fn main() { dotenv::dotenv().expect("Failed to load .env file"); @@ -139,7 +119,7 @@ async fn main() { execute_self_messages: false, // dynamic prefix support dynamic_prefix: Some(|context: PartialContext| { - Box::pin(async move { crate::utilities::command::get_prefix(context).await }) + Box::pin(async move { bismarck_utilities::command::get_prefix(context).await }) }), ..Default::default() }, @@ -180,7 +160,7 @@ async fn main() { Box::pin(event_handler(context, event, framework, data)) }, pre_command: |context| { - Box::pin(async move { crate::utilities::command::pre_command(context).await }) + Box::pin(async move { bismarck_utilities::command::pre_command(context).await }) }, on_error: |error| Box::pin(on_error(error)), ..Default::default() From 745a8468e6c98f14c2311b5c7a178b3a06249d9a Mon Sep 17 00:00:00 2001 From: paninizer <101371138+paninizer@users.noreply.github.com> Date: Fri, 26 Apr 2024 19:13:04 -0400 Subject: [PATCH 4/8] reformat + split off to core functions --- bismarck_commands/src/info.rs | 2 +- bismarck_commands/src/wiki.rs | 6 +++++- bismarck_core/src/context.rs | 4 ++-- bismarck_core/src/data.rs | 6 +++--- bismarck_core/src/error.rs | 4 ++-- bismarck_core/src/lib.rs | 12 +++++++++++- bismarck_utilities/src/command.rs | 5 ++++- bismarck_utilities/src/lib.rs | 1 - src/main.rs | 8 ++++---- 9 files changed, 32 insertions(+), 16 deletions(-) diff --git a/bismarck_commands/src/info.rs b/bismarck_commands/src/info.rs index 42e6785..536a0d8 100644 --- a/bismarck_commands/src/info.rs +++ b/bismarck_commands/src/info.rs @@ -1,7 +1,7 @@ use std::sync::atomic::Ordering; -use bismarck_utilities::git::{get_current_branch, get_head_revision}; use bismarck_core::{context::Context, error::Error}; +use bismarck_utilities::git::{get_current_branch, get_head_revision}; use git2::Repository; use poise::{serenity_prelude as serenity, CreateReply}; diff --git a/bismarck_commands/src/wiki.rs b/bismarck_commands/src/wiki.rs index 45c01ea..4efecb8 100644 --- a/bismarck_commands/src/wiki.rs +++ b/bismarck_commands/src/wiki.rs @@ -1,7 +1,11 @@ use poise::CreateReply; use serenity::all::{CreateEmbed, CreateMessage, CreateSelectMenu, CreateSelectMenuOption}; -use bismarck_core::{context::Context, error::Error, types::{WikiQuery, QueryContainer}}; +use bismarck_core::{ + context::Context, + error::Error, + types::{QueryContainer, WikiQuery}, +}; /// Shows Wikipedia search results. #[poise::command( diff --git a/bismarck_core/src/context.rs b/bismarck_core/src/context.rs index 1598e91..ac8e8f6 100644 --- a/bismarck_core/src/context.rs +++ b/bismarck_core/src/context.rs @@ -1,6 +1,6 @@ -use poise; use crate::data::Data; use crate::error::Error; +use poise; pub type Context<'a> = poise::Context<'a, Data, Error>; -pub type PartialContext<'a> = poise::PartialContext<'a, Data, Error>; \ No newline at end of file +pub type PartialContext<'a> = poise::PartialContext<'a, Data, Error>; diff --git a/bismarck_core/src/data.rs b/bismarck_core/src/data.rs index 00af65e..7d7b57e 100644 --- a/bismarck_core/src/data.rs +++ b/bismarck_core/src/data.rs @@ -1,9 +1,9 @@ +use crate::types::{GuildSettings, User}; use dashmap::DashMap; use poise::serenity_prelude as serenity; +use sqlx::SqlitePool; use std::sync::atomic::{AtomicBool, AtomicU64}; use std::sync::Arc; -use sqlx::SqlitePool; -use crate::types::{GuildSettings, User}; #[derive(Debug)] pub struct Data { @@ -16,4 +16,4 @@ pub struct Data { pub songs_played: DashMap, pub shard_manager: Arc, pub is_loop_running: AtomicBool, -} // User data, which is stored and accessible in all command invocations \ No newline at end of file +} // User data, which is stored and accessible in all command invocations diff --git a/bismarck_core/src/error.rs b/bismarck_core/src/error.rs index 51e2815..4141cbb 100644 --- a/bismarck_core/src/error.rs +++ b/bismarck_core/src/error.rs @@ -1,7 +1,7 @@ -use poise; use data::Data; +use poise; use crate::data; pub type Error = Box; -pub type FrameworkError<'a> = poise::FrameworkError<'a, Data, Error>; \ No newline at end of file +pub type FrameworkError<'a> = poise::FrameworkError<'a, Data, Error>; diff --git a/bismarck_core/src/lib.rs b/bismarck_core/src/lib.rs index eefc42f..76cca9e 100644 --- a/bismarck_core/src/lib.rs +++ b/bismarck_core/src/lib.rs @@ -1,5 +1,15 @@ +use poise::serenity_prelude as serenity; pub mod context; pub mod data; pub mod error; -pub mod types; \ No newline at end of file +pub mod types; + +pub async fn gateway_intents() -> serenity::GatewayIntents { + serenity::GatewayIntents::non_privileged() + | serenity::GatewayIntents::GUILD_MEMBERS + | serenity::GatewayIntents::GUILD_MODERATION + | serenity::GatewayIntents::GUILD_MESSAGES + | serenity::GatewayIntents::DIRECT_MESSAGES + | serenity::GatewayIntents::MESSAGE_CONTENT +} diff --git a/bismarck_utilities/src/command.rs b/bismarck_utilities/src/command.rs index ac0b0a9..325e7d6 100644 --- a/bismarck_utilities/src/command.rs +++ b/bismarck_utilities/src/command.rs @@ -4,7 +4,10 @@ use sqlx::sqlite::SqliteQueryResult; use tokio::time::Instant; use tracing::{debug, error}; -use bismarck_core::{context::{Context, PartialContext}, error::Error}; +use bismarck_core::{ + context::{Context, PartialContext}, + error::Error, +}; pub async fn get_prefix(context: PartialContext<'_>) -> Result, Error> { if let Some(guild_id) = context.guild_id { diff --git a/bismarck_utilities/src/lib.rs b/bismarck_utilities/src/lib.rs index 0d3eeec..6cdaf91 100644 --- a/bismarck_utilities/src/lib.rs +++ b/bismarck_utilities/src/lib.rs @@ -1,4 +1,3 @@ - pub mod command; pub mod embeds; pub mod git; diff --git a/src/main.rs b/src/main.rs index d174f01..5d8bbb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ use bismarck_core::context::PartialContext; use bismarck_core::data::Data; +use bismarck_core::types::{GuildSettings, User}; +use bismarck_events::event_handler::event_handler; +use bismarck_events::on_error::on_error; use dashmap::DashMap; use poise::serenity_prelude as serenity; use std::env; @@ -7,9 +10,6 @@ use std::sync::atomic::{AtomicBool, AtomicU64}; use std::{sync::Arc, time::Duration}; use tokio::time::sleep; use tracing::{error, info}; -use bismarck_events::event_handler::event_handler; -use bismarck_events::on_error::on_error; -use bismarck_core::types::{GuildSettings, User}; use bismarck_commands::{ info::*, math::*, moderation::*, neko::*, owner::*, setup::*, utilities::*, wiki::*, @@ -20,7 +20,7 @@ async fn main() { dotenv::dotenv().expect("Failed to load .env file"); // gets token, exits if no token let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment"); - let intents = serenity::GatewayIntents::all(); + let intents = bismarck_core::gateway_intents().await; // Initialize the logger to use environment variables. // From 9a6c6b4b4652891368e265a206a386a91f7aaa5c Mon Sep 17 00:00:00 2001 From: Aimar Ibarra Date: Mon, 29 Apr 2024 22:12:23 +0200 Subject: [PATCH 5/8] Calculator implementation added, command needs to be done. --- bismarck_commands/src/math.rs | 712 ++++++++++++++++++++++++++++++---- src/main.rs | 5 +- 2 files changed, 640 insertions(+), 77 deletions(-) diff --git a/bismarck_commands/src/math.rs b/bismarck_commands/src/math.rs index e76fe47..5399a70 100644 --- a/bismarck_commands/src/math.rs +++ b/bismarck_commands/src/math.rs @@ -1,102 +1,668 @@ -use bismarck_core::{context::Context, error::Error}; +use std::{iter, mem::transmute}; -/// Multiplies two numbers. +/* #[poise::command( prefix_command, slash_command, category = "Math", - required_bot_permissions = "SEND_MESSAGES" + required_permissions = "SEND_MESSAGES", + aliases("calc", "eval"), + user_cooldown = 2 )] -pub async fn multiply( +pub async fn math( context: Context<'_>, - #[description = "One number to be multiplied"] one: Option, - #[description = "Another number to be multiplied"] two: Option, + #[description = "Expression to evaluate"] expr: String, ) -> Result<(), Error> { - let one = one.unwrap_or(1.0); - let two = two.unwrap_or(1.0); + let toks = tokenize(&expr)?; + let ops = Parser::new(toks).parse()?; + match execute(ops)? { + Ret::I32(n) => todo!("Output i32"), + Ret::U32(n) => todo!("Output u32"), + Ret::F32(n) => todo!("Output f32"), + } + Ok(()) +} +*/ - let product = one * two; +mod eval { + use super::*; - let _ = context - .say(format!("{} * {} = {}", one, two, product)) - .await; + mod test_u32 { + use super::*; - Ok(()) + #[allow(dead_code)] + fn tst(s: &str, e: u32) { + let toks = tokenize(s).unwrap(); + let ops = Parser::new(toks).parse().unwrap(); + let r = execute(ops).unwrap(); + assert_eq!(r, Ret::U32(e)); + } + + #[test] + fn add() { + tst("1_u + 2_u", 3); + tst(&format!("{}_u + 1_u", u32::MAX), 0); + } + + #[test] + fn sub() { + tst("2_u - 1_u", 1); + tst("0_u - 1_u", u32::MAX); + } + + #[test] + fn mul() { + tst("2_u * 3_u", 6); + } + + #[test] + fn div() { + tst("6_u / 2_u", 3); + } + } + + mod test_i32 { + use super::*; + + #[allow(dead_code)] + fn tst(s: &str, e: i32) { + let toks = tokenize(s).unwrap(); + let ops = Parser::new(toks).parse().unwrap(); + let r = execute(ops).unwrap(); + assert_eq!(r, Ret::I32(e)); + } + + #[test] + fn add() { + tst("1 + 2", 3); + tst(&format!("{}", i32::MAX), i32::MAX); + } + + #[test] + fn sub() { + tst("1 - 2", -1); + tst(&format!("{} - 1", i32::MIN), i32::MAX); + } + + #[test] + fn mul() { + tst("2 * 3", 6); + } + + #[test] + fn div() { + tst("6 / 3", 2); + } + } + + mod test_f32 { + use super::*; + + #[allow(dead_code)] + fn tst(s: &str, e: f32) { + let toks = tokenize(s).unwrap(); + let ops = Parser::new(toks).parse().unwrap(); + let r = execute(ops).unwrap(); + assert_eq!(r, Ret::F32(e)); + } + + #[test] + fn add() { + tst("1.0 + 2.4", 3.4); + } + + #[test] + fn sub() { + tst("1. - 2.0", -1.); + } + + #[test] + fn mul() { + tst("2.0 * 0.5", 1.); + } + + #[test] + fn div() { + tst("6.0 / 0.5", 12.); + } + } + + mod test_pred { + use super::*; + #[allow(dead_code)] + fn tst(s: &str, e: i32) { + let toks = tokenize(s).unwrap(); + let ops = Parser::new(toks).parse().unwrap(); + let r = execute(ops).unwrap(); + assert_eq!(r, Ret::I32(e)); + } + + #[test] + fn chain() { + tst("1 + 2 + 3", 6); + } + + #[test] + fn mulsum() { + tst("2 + 3 * 4", 2 + 3 * 4); + tst("3 * 4 + 2", 3 * 4 + 2); + } + + #[test] + fn div() { + tst("2 * 10 / 3", 2 * 10 / 3); + tst("4 / 2 / 2", 4 / 2 / 2); + } + + #[test] + fn paren() { + tst("(2 + 3) * 4", (2 + 3) * 4); + tst("4 * (2 + 3)", 4 * (2 + 3)); + } + } } -/// Adds two numbers. -#[poise::command( - prefix_command, - slash_command, - category = "Math", - required_bot_permissions = "SEND_MESSAGES" -)] -pub async fn add( - context: Context<'_>, - #[description = "One number to be added"] one: Option, - #[description = "Another number to be added"] two: Option, -) -> Result<(), Error> { - let one = one.unwrap_or(1.0); - let two = two.unwrap_or(1.0); +#[derive(Debug, Clone)] +#[repr(u32)] +enum Operation { + Goto, + Test, + Nop, - let product = one + two; + Const32, - let _ = context - .say(format!("{} + {} = {}", one, two, product)) - .await; + ExitF32, + ExitI32, + ExitU32, - Ok(()) + F32Neg, + F32Add, + F32Sub, + F32Mul, + F32Div, + F32Lt, + F32Eq, + F32CastI32S, + F32CastI32U, + + I32Neg, + I32Add, + I32Sub, + I32Mul, + I32DivS, + I32DivU, + I32Lt, + I32Eq, + I32CastF32S, + I32CastF32U, } -/// Divides two numbers. -#[poise::command( - prefix_command, - slash_command, - category = "Math", - required_bot_permissions = "SEND_MESSAGES" -)] -pub async fn divide( - context: Context<'_>, - #[description = "Number to be divided"] dividend: f64, - #[description = "A number to divide One"] divisor: f64, -) -> Result<(), Error> { - let one = dividend; - let two = divisor; +struct Stack { + data: Vec, +} + +impl Stack { + fn new() -> Self { + Self { data: Vec::new() } + } - if two == 0.0 { - let _ = context.say("Divisor cannot be 0!").await; - return Ok(()); + fn pop(&mut self) -> u32 { + unsafe { self.data.pop().unwrap_unchecked() } } - let quotient = one / two; + fn pops(&mut self) -> i32 { + unsafe { transmute::(self.pop()) } + } - let _ = context - .say(format!("{} / {} = {}", one, two, quotient)) - .await; + fn popf(&mut self) -> f32 { + unsafe { transmute::(self.pop()) } + } - Ok(()) + fn push(&mut self, v: u32) { + self.data.push(v); + } + + fn pushb(&mut self, v: bool) { + self.push(v as u32); + } + + fn pushf(&mut self, v: f32) { + self.push(unsafe { transmute::(v) }); + } + + fn pushs(&mut self, v: i32) { + self.push(unsafe { transmute::(v) }); + } } -/// Subtracts two numbers. -#[poise::command( - prefix_command, - slash_command, - category = "Math", - required_bot_permissions = "SEND_MESSAGES" -)] -pub async fn subtract( - context: Context<'_>, - #[description = "One number to be subtracted"] one: Option, - #[description = "Another number to be subtracted"] two: Option, -) -> Result<(), Error> { - let one = one.unwrap_or(1.0); - let two = two.unwrap_or(1.0); +#[derive(Debug, Clone, PartialEq)] +enum Ret { + I32(i32), + F32(f32), + U32(u32), +} + +struct ByteCode(Vec); + +impl std::fmt::Debug for ByteCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let mut asnum = false; + for code in self.0.iter() { + if asnum { + write!(f, "{:x}\n", code)?; + asnum = false; + } else { + write!(f, "{:?}", unsafe { transmute::(*code) })?; + if *code == Operation::Const32 as u32 { + asnum = true; + write!(f, " ")?; + } else { + write!(f, "\n")?; + } + } + } + Ok(()) + } +} - let difference = one - two; +fn execute(operations: ByteCode) -> Result { + let mut i = 0usize; + let mut s = Stack::new(); - let _ = context - .say(format!("{} - {} = {}", one, two, difference)) - .await; + loop { + use Operation as Op; + let e = operations.0[i]; + match unsafe { transmute::(e) } { + Op::Goto => { + i = s.pop() as usize; + continue; + } + Op::Test => { + if s.pop() == 0 { + i = i + 1; + } + } + Op::Nop => (), + Op::Const32 => { + i = i + 1; + s.push(operations.0[i]); + } + Op::I32CastF32S => { + let a = s.pops(); + s.pushf(a as f32); + } + Op::I32CastF32U => { + let a = s.pop(); + s.pushf(a as f32); + } + Op::F32CastI32S => { + let a = s.popf(); + s.pushs(a as i32); + } + Op::F32CastI32U => { + let a = s.popf(); + s.push(a as u32); + } + Op::ExitI32 => { + return Ok(Ret::I32(s.pops())); + } + Op::ExitF32 => { + return Ok(Ret::F32(s.popf())); + } + Op::ExitU32 => { + return Ok(Ret::U32(s.pop())); + } + Op::I32Neg => { + let a = s.pop(); + s.push((0u32).wrapping_sub(a)); + } + Op::F32Neg => { + let a = s.popf(); + s.pushf(-a); + } + binop => { + let r = s.pop(); + let l = s.pop(); + fn asf32(arg: u32) -> f32 { + unsafe { transmute::(arg) } + } - Ok(()) + fn asi32(arg: u32) -> i32 { + unsafe { transmute::(arg) } + } + + match binop { + Op::I32Add => s.push(l.wrapping_add(r)), + Op::I32Sub => s.push(l.wrapping_sub(r)), + Op::I32Mul => s.push(l.wrapping_mul(r)), + Op::I32DivS => s.pushs(asi32(l) / asi32(r)), + Op::I32DivU => { + if r == 0 { + return Err("Division by zero"); + } else { + s.push(l / r); + } + } + Op::I32Lt => { + if r == 0 { + return Err("Division by zero"); + } else { + s.pushb(l < r); + } + } + Op::I32Eq => s.pushb(l == r), + + Op::F32Add => s.pushf(asf32(l) + asf32(r)), + Op::F32Sub => s.pushf(asf32(l) - asf32(r)), + Op::F32Mul => s.pushf(asf32(l) * asf32(r)), + Op::F32Div => s.pushf(asf32(l) / asf32(r)), + Op::F32Eq => s.pushb(asf32(l) == asf32(r)), + Op::F32Lt => s.pushb(asf32(l) < asf32(r)), + + Op::Goto + | Op::Test + | Op::Const32 + | Op::I32CastF32S + | Op::I32CastF32U + | Op::F32CastI32S + | Op::F32CastI32U + | Op::ExitI32 + | Op::ExitF32 + | Op::I32Neg + | Op::F32Neg + | Op::ExitU32 + | Op::Nop => { + unreachable!() + } + } + } + } + i = i + 1; + } +} + +#[repr(u32)] +#[derive(PartialEq, Eq, Clone, Debug)] +enum Type { + I32, + F32, + U32, +} + +impl Type { + fn cast(&self, typ: Type) -> Operation { + use Operation::*; + use Type as T; + match (self, typ) { + (T::I32, T::F32) => I32CastF32S, + (T::U32, T::F32) => I32CastF32U, + + (T::F32, T::I32) => F32CastI32S, + (T::F32, T::U32) => F32CastI32U, + _ => Nop, + } + } + + fn neg(&self) -> Operation { + match self { + Type::I32 | Type::U32 => Operation::I32Neg, + Type::F32 => Operation::F32Neg, + } + } + + fn mul(&self) -> Operation { + match self { + Type::I32 | Type::U32 => Operation::I32Mul, + Type::F32 => Operation::F32Mul, + } + } + + fn add(&self) -> Operation { + match self { + Type::I32 | Type::U32 => Operation::I32Add, + Type::F32 => Operation::F32Add, + } + } + + fn sub(&self) -> Operation { + match self { + Type::I32 | Type::U32 => Operation::I32Sub, + Type::F32 => Operation::F32Sub, + } + } + + fn div(&self) -> Operation { + match self { + Type::I32 => Operation::I32DivS, + Type::U32 => Operation::I32DivU, + Type::F32 => Operation::F32Div, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +enum Opc { + Plus, + Minus, + Star, + Slash, +} + +impl Opc { + fn new(c: char) -> Option { + match c { + '+' => Some(Opc::Plus), + '-' => Some(Opc::Minus), + '*' => Some(Opc::Star), + '/' => Some(Opc::Slash), + _ => None, + } + } + + fn pred(&self) -> i32 { + match self { + Opc::Plus => 0, + Opc::Minus => 0, + Opc::Star => 1, + Opc::Slash => 1, + } + } + + fn code(&self, typ: Type) -> Operation { + match self { + Opc::Plus => typ.add(), + Opc::Minus => typ.sub(), + Opc::Star => typ.mul(), + Opc::Slash => typ.div(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +enum Token { + Eof, + LParen, + RParen, + Operator(Opc), + Symbol(String), + I32(u32), + F32(f32), + Cast(Type), +} + +fn tokenize(input: &str) -> Result, &'static str> { + let mut it = input.chars().peekable(); + let mut v: Vec = Vec::new(); + + while let Some(c) = it.next() { + match c { + _ if c.is_whitespace() => (), + _ if Opc::new(c).is_some() => v.push(Token::Operator(Opc::new(c).unwrap())), + '(' => v.push(Token::LParen), + ')' => v.push(Token::RParen), + '_' => match it.next() { + Some('i') => v.push(Token::Cast(Type::I32)), + Some('f') => v.push(Token::Cast(Type::F32)), + Some('u') => v.push(Token::Cast(Type::U32)), + _ => return Err("Invalid use of '_' (casting operator)"), + }, + _ if c.is_ascii_digit() => { + let n: String = iter::once(c) + .chain(iter::from_fn(|| { + it.by_ref().next_if(|c| c.is_ascii_digit()) + })) + .collect(); + if it.next_if_eq(&'.').is_some() { + let f: String = n + .chars() + .chain(iter::once('.')) + .chain(iter::from_fn(|| { + it.by_ref().next_if(|c| c.is_ascii_digit()) + })) + .collect(); + match f.parse::() { + Err(_) => return Err("Invalid float format"), + Ok(f) => v.push(Token::F32(f)), + } + } else { + match n.parse::() { + Err(_) => return Err("How did we get here? (Invalid int format)"), + Ok(n) => v.push(Token::I32(n)), + } + } + } + _ if c.is_alphabetic() => v.push(Token::Symbol( + iter::from_fn(|| it.by_ref().next_if(|c| c.is_alphanumeric())).collect(), + )), + _ => return Err("Invalid character"), + } + } + Ok(v) +} + +struct Parser { + output: ByteCode, + tokens: Vec, + index: usize, +} + +impl Parser { + fn new(toks: Vec) -> Self { + Self { + output: ByteCode(Vec::new()), + tokens: toks, + index: 0, + } + } + + fn add_op(&mut self, code: Operation) { + self.output.0.push(code as u32); + } + + fn add_32(&mut self, v: u32) { + self.output.0.push(v); + } + + fn peek(&mut self) -> Token { + self.tokens + .get(self.index) + .map(|t| t.clone()) + .unwrap_or(Token::Eof) + } + + fn eat(&mut self) { + self.index = self.index + 1 + } + + fn next(&mut self) -> Token { + let t = self.peek(); + self.eat(); + t + } + + fn parse(mut self) -> Result { + let p = self.primary()?; + let e = self.expression(p, 0)?; + match self.next() { + Token::Eof => { + match e { + Type::I32 => self.add_op(Operation::ExitI32), + Type::U32 => self.add_op(Operation::ExitU32), + Type::F32 => self.add_op(Operation::ExitF32), + } + Ok(self.output) + } + _ => Err("Expected end of input"), + } + } + + fn expression(&mut self, mut lhs: Type, pred: i32) -> Result { + loop { + let op = match self.peek() { + Token::Operator(op) if op.pred() >= pred => op, + _ => break, + }; + self.eat(); + + let mut rhs = self.primary()?; + loop { + let op2 = match self.peek() { + Token::Operator(op2) if op2.pred() > op.pred() => op2, + _ => break, + }; + rhs = self.expression(rhs, op2.pred())?; + self.next(); + } + if rhs != lhs { + return Err("Type mismatch"); + } + self.add_op(op.code(lhs)); + lhs = rhs; + } + Ok(lhs) + } + + fn primary(&mut self) -> Result { + let r = match self.next() { + Token::LParen => { + let p = self.primary()?; + let r = self.expression(p, 0)?; + match self.next() { + Token::RParen => r, + _ => Err("Expected ')'")?, + } + } + Token::Operator(Opc::Minus) => { + let p = self.primary()?; + self.add_op(p.neg()); + p + } + Token::Operator(Opc::Plus) => { + let p = self.primary()?; + p + } + Token::F32(n) => { + self.add_op(Operation::Const32); + self.add_32(unsafe { transmute::(n) }); + Type::F32 + } + Token::I32(n) => { + self.add_op(Operation::Const32); + self.add_32(n); + Type::I32 + } + _ => Err("Expected a value")?, + }; + Ok(match self.peek() { + Token::Cast(t) => { + self.eat(); + match r.cast(t.clone()) { + Operation::Nop => (), + t => self.add_op(t), + } + t + } + _ => r, + }) + } } diff --git a/src/main.rs b/src/main.rs index 5d8bbb4..d2f9263 100644 --- a/src/main.rs +++ b/src/main.rs @@ -130,10 +130,7 @@ async fn main() { user_avatars(), bot_stat(), // Math commands - multiply(), - add(), - divide(), - subtract(), + // TODO: math(), // Moderation commands ban(), kick(), From 70337057b9825154edd89f73f87b935f4a8a4b04 Mon Sep 17 00:00:00 2001 From: paninizer <101371138+paninizer@users.noreply.github.com> Date: Mon, 29 Apr 2024 21:02:17 -0400 Subject: [PATCH 6/8] removed some unsafe next fix is to entirely remove unsafe but I do not know how --- bismarck_commands/src/math.rs | 27 +++++++++++++-------------- bismarck_commands/src/wish.rs | 12 ++++++------ src/main.rs | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/bismarck_commands/src/math.rs b/bismarck_commands/src/math.rs index 5399a70..acafaf5 100644 --- a/bismarck_commands/src/math.rs +++ b/bismarck_commands/src/math.rs @@ -215,7 +215,7 @@ impl Stack { } fn popf(&mut self) -> f32 { - unsafe { transmute::(self.pop()) } + f32::from_bits(self.pop()) } fn push(&mut self, v: u32) { @@ -227,7 +227,7 @@ impl Stack { } fn pushf(&mut self, v: f32) { - self.push(unsafe { transmute::(v) }); + self.push(v.to_bits()); } fn pushs(&mut self, v: i32) { @@ -249,7 +249,7 @@ impl std::fmt::Debug for ByteCode { let mut asnum = false; for code in self.0.iter() { if asnum { - write!(f, "{:x}\n", code)?; + writeln!(f, "{:x}", code)?; asnum = false; } else { write!(f, "{:?}", unsafe { transmute::(*code) })?; @@ -257,7 +257,7 @@ impl std::fmt::Debug for ByteCode { asnum = true; write!(f, " ")?; } else { - write!(f, "\n")?; + writeln!(f)?; } } } @@ -279,12 +279,12 @@ fn execute(operations: ByteCode) -> Result { } Op::Test => { if s.pop() == 0 { - i = i + 1; + i += 1; } } Op::Nop => (), Op::Const32 => { - i = i + 1; + i += 1; s.push(operations.0[i]); } Op::I32CastF32S => { @@ -324,7 +324,7 @@ fn execute(operations: ByteCode) -> Result { let r = s.pop(); let l = s.pop(); fn asf32(arg: u32) -> f32 { - unsafe { transmute::(arg) } + f32::from_bits(arg) } fn asi32(arg: u32) -> i32 { @@ -377,7 +377,7 @@ fn execute(operations: ByteCode) -> Result { } } } - i = i + 1; + i += 1; } } @@ -565,13 +565,12 @@ impl Parser { fn peek(&mut self) -> Token { self.tokens - .get(self.index) - .map(|t| t.clone()) + .get(self.index).cloned() .unwrap_or(Token::Eof) } fn eat(&mut self) { - self.index = self.index + 1 + self.index += 1 } fn next(&mut self) -> Token { @@ -638,12 +637,12 @@ impl Parser { p } Token::Operator(Opc::Plus) => { - let p = self.primary()?; - p + + self.primary()? } Token::F32(n) => { self.add_op(Operation::Const32); - self.add_32(unsafe { transmute::(n) }); + self.add_32(n.to_bits()); Type::F32 } Token::I32(n) => { diff --git a/bismarck_commands/src/wish.rs b/bismarck_commands/src/wish.rs index 8c8b247..ba2808f 100644 --- a/bismarck_commands/src/wish.rs +++ b/bismarck_commands/src/wish.rs @@ -60,8 +60,8 @@ mod wish_tests { let roll; (roll, state) = wish.roll(state, &mut rng); match roll.kind { - RollKind::FiveStar => s5 = s5 + 1., - RollKind::FourStar => s4 = s4 + 1., + RollKind::FiveStar => s5 += 1., + RollKind::FourStar => s4 += 1., _ => (), } } @@ -95,10 +95,10 @@ mod wish_tests { let roll; (roll, state) = wish.roll(state, &mut rng); match roll.kind { - RollKind::FiveStar => s5 = s5 + 1., - RollKind::FiveStarFeatured => s5 = s5 + 1., - RollKind::FourStar => s4 = s4 + 1., - RollKind::FourStarFeatured => s4 = s4 + 1., + RollKind::FiveStar => s5 += 1., + RollKind::FiveStarFeatured => s5 += 1., + RollKind::FourStar => s4 += 1., + RollKind::FourStarFeatured => s4 += 1., _ => (), } } diff --git a/src/main.rs b/src/main.rs index d2f9263..3f93212 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use tokio::time::sleep; use tracing::{error, info}; use bismarck_commands::{ - info::*, math::*, moderation::*, neko::*, owner::*, setup::*, utilities::*, wiki::*, + info::*, moderation::*, neko::*, owner::*, setup::*, utilities::*, wiki::*, }; #[tokio::main] From c35c9e835f5c167e718817a061a2646c77107ac4 Mon Sep 17 00:00:00 2001 From: Aimar Ibarra Date: Sat, 4 May 2024 21:02:23 +0200 Subject: [PATCH 7/8] Got rid of the VM, now the parser evaluates. --- bismarck_commands/src/math.rs | 512 ++++++++-------------------------- 1 file changed, 122 insertions(+), 390 deletions(-) diff --git a/bismarck_commands/src/math.rs b/bismarck_commands/src/math.rs index acafaf5..72f8612 100644 --- a/bismarck_commands/src/math.rs +++ b/bismarck_commands/src/math.rs @@ -1,4 +1,4 @@ -use std::{iter, mem::transmute}; +use std::iter::{self, Peekable}; /* #[poise::command( @@ -27,421 +27,198 @@ pub async fn math( mod eval { use super::*; + #[allow(dead_code)] + fn p(s: &str) -> Value { + Parser::new(tokenize(s).unwrap().into_iter()) + .parse() + .unwrap() + } + + #[allow(unused_imports)] mod test_u32 { use super::*; - #[allow(dead_code)] - fn tst(s: &str, e: u32) { - let toks = tokenize(s).unwrap(); - let ops = Parser::new(toks).parse().unwrap(); - let r = execute(ops).unwrap(); - assert_eq!(r, Ret::U32(e)); - } - #[test] fn add() { - tst("1_u + 2_u", 3); - tst(&format!("{}_u + 1_u", u32::MAX), 0); + assert_eq!(p("1_u + 2_u"), Value::U32(3)); + // assert_eq!(p(&format!("{}_u + 1_u", u32::MAX)), Value::U32(0)); // will panic } #[test] fn sub() { - tst("2_u - 1_u", 1); - tst("0_u - 1_u", u32::MAX); + assert_eq!(p("2_u - 1_u"), Value::U32(1)); + // assert_eq!(p("0_u - 1_u"), Value::U32(u32::MAX)); // will panic } #[test] fn mul() { - tst("2_u * 3_u", 6); + assert_eq!(p("2_u * 3_u"), Value::U32(6)); } #[test] fn div() { - tst("6_u / 2_u", 3); + assert_eq!(p("6_u / 2_u"), Value::U32(3)); } } + #[allow(unused_imports)] mod test_i32 { use super::*; - #[allow(dead_code)] - fn tst(s: &str, e: i32) { - let toks = tokenize(s).unwrap(); - let ops = Parser::new(toks).parse().unwrap(); - let r = execute(ops).unwrap(); - assert_eq!(r, Ret::I32(e)); - } - #[test] fn add() { - tst("1 + 2", 3); - tst(&format!("{}", i32::MAX), i32::MAX); + assert_eq!(p("1 + 2"), Value::I32(3)); + assert_eq!(p(&format!("{}", i32::MAX)), Value::I32(i32::MAX)); } #[test] fn sub() { - tst("1 - 2", -1); - tst(&format!("{} - 1", i32::MIN), i32::MAX); + assert_eq!(p("1 - 2"), Value::I32(-1)); + // assert_eq!(p(&format!("{} - 2", i32::MIN + 1)), Value::I32(i32::MAX)); // will panic } #[test] fn mul() { - tst("2 * 3", 6); + assert_eq!(p("2 * 3"), Value::I32(6)); } #[test] fn div() { - tst("6 / 3", 2); + assert_eq!(p("6 / 3"), Value::I32(2)); } } + #[allow(unused_imports)] mod test_f32 { use super::*; - #[allow(dead_code)] - fn tst(s: &str, e: f32) { - let toks = tokenize(s).unwrap(); - let ops = Parser::new(toks).parse().unwrap(); - let r = execute(ops).unwrap(); - assert_eq!(r, Ret::F32(e)); - } - #[test] fn add() { - tst("1.0 + 2.4", 3.4); + assert_eq!(p("1.0 + 2.4"), Value::F32(3.4)); } #[test] fn sub() { - tst("1. - 2.0", -1.); + assert_eq!(p("1. - 2.0"), Value::F32(-1.)); } #[test] fn mul() { - tst("2.0 * 0.5", 1.); + assert_eq!(p("2.0 * 0.5"), Value::F32(1.)); } #[test] fn div() { - tst("6.0 / 0.5", 12.); + assert_eq!(p("6.0 / 0.5"), Value::F32(12.)); } } + #[allow(unused_imports)] mod test_pred { use super::*; - #[allow(dead_code)] - fn tst(s: &str, e: i32) { - let toks = tokenize(s).unwrap(); - let ops = Parser::new(toks).parse().unwrap(); - let r = execute(ops).unwrap(); - assert_eq!(r, Ret::I32(e)); - } #[test] fn chain() { - tst("1 + 2 + 3", 6); + assert_eq!(p("1 + 2 + 3"), Value::I32(6)); } #[test] fn mulsum() { - tst("2 + 3 * 4", 2 + 3 * 4); - tst("3 * 4 + 2", 3 * 4 + 2); + assert_eq!(p("2 + 3 * 4"), Value::I32(2 + 3 * 4)); + assert_eq!(p("3 * 4 + 2"), Value::I32(3 * 4 + 2)); } #[test] fn div() { - tst("2 * 10 / 3", 2 * 10 / 3); - tst("4 / 2 / 2", 4 / 2 / 2); + assert_eq!(p("2 * 10 / 3"), Value::I32(2 * 10 / 3)); + assert_eq!(p("4 / 2 / 2"), Value::I32(4 / 2 / 2)); } #[test] fn paren() { - tst("(2 + 3) * 4", (2 + 3) * 4); - tst("4 * (2 + 3)", 4 * (2 + 3)); + assert_eq!(p("(2 + 3) * 4"), Value::I32((2 + 3) * 4)); + assert_eq!(p("4 * (2 + 3)"), Value::I32(4 * (2 + 3))); } } } -#[derive(Debug, Clone)] -#[repr(u32)] -enum Operation { - Goto, - Test, - Nop, - - Const32, - - ExitF32, - ExitI32, - ExitU32, - - F32Neg, - F32Add, - F32Sub, - F32Mul, - F32Div, - F32Lt, - F32Eq, - F32CastI32S, - F32CastI32U, - - I32Neg, - I32Add, - I32Sub, - I32Mul, - I32DivS, - I32DivU, - I32Lt, - I32Eq, - I32CastF32S, - I32CastF32U, -} - -struct Stack { - data: Vec, -} - -impl Stack { - fn new() -> Self { - Self { data: Vec::new() } - } - - fn pop(&mut self) -> u32 { - unsafe { self.data.pop().unwrap_unchecked() } - } - - fn pops(&mut self) -> i32 { - unsafe { transmute::(self.pop()) } - } - - fn popf(&mut self) -> f32 { - f32::from_bits(self.pop()) - } - - fn push(&mut self, v: u32) { - self.data.push(v); - } - - fn pushb(&mut self, v: bool) { - self.push(v as u32); - } - - fn pushf(&mut self, v: f32) { - self.push(v.to_bits()); - } - - fn pushs(&mut self, v: i32) { - self.push(unsafe { transmute::(v) }); - } -} - #[derive(Debug, Clone, PartialEq)] -enum Ret { +pub enum Value { I32(i32), F32(f32), U32(u32), } -struct ByteCode(Vec); - -impl std::fmt::Debug for ByteCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let mut asnum = false; - for code in self.0.iter() { - if asnum { - writeln!(f, "{:x}", code)?; - asnum = false; - } else { - write!(f, "{:?}", unsafe { transmute::(*code) })?; - if *code == Operation::Const32 as u32 { - asnum = true; - write!(f, " ")?; - } else { - writeln!(f)?; - } - } - } - Ok(()) - } -} - -fn execute(operations: ByteCode) -> Result { - let mut i = 0usize; - let mut s = Stack::new(); - - loop { - use Operation as Op; - let e = operations.0[i]; - match unsafe { transmute::(e) } { - Op::Goto => { - i = s.pop() as usize; - continue; - } - Op::Test => { - if s.pop() == 0 { - i += 1; - } - } - Op::Nop => (), - Op::Const32 => { - i += 1; - s.push(operations.0[i]); - } - Op::I32CastF32S => { - let a = s.pops(); - s.pushf(a as f32); - } - Op::I32CastF32U => { - let a = s.pop(); - s.pushf(a as f32); - } - Op::F32CastI32S => { - let a = s.popf(); - s.pushs(a as i32); - } - Op::F32CastI32U => { - let a = s.popf(); - s.push(a as u32); - } - Op::ExitI32 => { - return Ok(Ret::I32(s.pops())); - } - Op::ExitF32 => { - return Ok(Ret::F32(s.popf())); - } - Op::ExitU32 => { - return Ok(Ret::U32(s.pop())); - } - Op::I32Neg => { - let a = s.pop(); - s.push((0u32).wrapping_sub(a)); - } - Op::F32Neg => { - let a = s.popf(); - s.pushf(-a); - } - binop => { - let r = s.pop(); - let l = s.pop(); - fn asf32(arg: u32) -> f32 { - f32::from_bits(arg) - } - - fn asi32(arg: u32) -> i32 { - unsafe { transmute::(arg) } - } - - match binop { - Op::I32Add => s.push(l.wrapping_add(r)), - Op::I32Sub => s.push(l.wrapping_sub(r)), - Op::I32Mul => s.push(l.wrapping_mul(r)), - Op::I32DivS => s.pushs(asi32(l) / asi32(r)), - Op::I32DivU => { - if r == 0 { - return Err("Division by zero"); - } else { - s.push(l / r); - } - } - Op::I32Lt => { - if r == 0 { - return Err("Division by zero"); - } else { - s.pushb(l < r); - } - } - Op::I32Eq => s.pushb(l == r), - - Op::F32Add => s.pushf(asf32(l) + asf32(r)), - Op::F32Sub => s.pushf(asf32(l) - asf32(r)), - Op::F32Mul => s.pushf(asf32(l) * asf32(r)), - Op::F32Div => s.pushf(asf32(l) / asf32(r)), - Op::F32Eq => s.pushb(asf32(l) == asf32(r)), - Op::F32Lt => s.pushb(asf32(l) < asf32(r)), - - Op::Goto - | Op::Test - | Op::Const32 - | Op::I32CastF32S - | Op::I32CastF32U - | Op::F32CastI32S - | Op::F32CastI32U - | Op::ExitI32 - | Op::ExitF32 - | Op::I32Neg - | Op::F32Neg - | Op::ExitU32 - | Op::Nop => { - unreachable!() - } - } - } - } - i += 1; - } -} - -#[repr(u32)] #[derive(PartialEq, Eq, Clone, Debug)] -enum Type { +pub enum Type { I32, F32, U32, } -impl Type { - fn cast(&self, typ: Type) -> Operation { - use Operation::*; - use Type as T; - match (self, typ) { - (T::I32, T::F32) => I32CastF32S, - (T::U32, T::F32) => I32CastF32U, - - (T::F32, T::I32) => F32CastI32S, - (T::F32, T::U32) => F32CastI32U, - _ => Nop, - } +// TODO: Make it checked so it doesn't panic +macro_rules! mkbinop { + ($name:ident, $op:tt) => { + fn $name(self, other: Value) -> Result { + use Value as V; + match (self, other) { + (V::I32(l), V::I32(r)) => Ok(V::I32(l $op r)), + (V::U32(l), V::U32(r)) => Ok(V::U32(l $op r)), + (V::F32(l), V::F32(r)) => Ok(V::F32(l $op r)), + _ => Err("Type mismatch"), + } + } } +} - fn neg(&self) -> Operation { - match self { - Type::I32 | Type::U32 => Operation::I32Neg, - Type::F32 => Operation::F32Neg, - } - } +impl Value { + fn cast(self, typ: Type) -> Result { + use Type as T; + use Value as V; + Ok(match (self.clone(), typ) { + (V::I32(n), T::F32) => V::F32(n as f32), + (V::I32(n), T::U32) => { + if n < 0 { + Err("Cannot convert negative number to unsigned")? + } else { + V::U32(n as u32) + } + } + (V::I32(_), T::I32) => self, - fn mul(&self) -> Operation { - match self { - Type::I32 | Type::U32 => Operation::I32Mul, - Type::F32 => Operation::F32Mul, - } - } + (V::U32(n), T::F32) => V::F32(n as f32), + (V::U32(_), T::U32) => self, + (V::U32(n), T::I32) => { + if n > i32::MAX as u32 { + Err("Number too big to fit in signed integer")? + } else { + V::I32(n as i32) + } + } - fn add(&self) -> Operation { - match self { - Type::I32 | Type::U32 => Operation::I32Add, - Type::F32 => Operation::F32Add, - } + (V::F32(_), T::F32) => self, + (V::F32(n), T::U32) => V::U32(n as u32), + (V::F32(n), T::I32) => V::I32(n as i32), + }) } - fn sub(&self) -> Operation { + fn neg(self) -> Result { + use Value as V; match self { - Type::I32 | Type::U32 => Operation::I32Sub, - Type::F32 => Operation::F32Sub, + V::I32(n) => Ok(V::I32(-n)), + V::U32(_) => Err("Cannot negate unsigned value"), + V::F32(n) => Ok(V::F32(-n)), } } - fn div(&self) -> Operation { - match self { - Type::I32 => Operation::I32DivS, - Type::U32 => Operation::I32DivU, - Type::F32 => Operation::F32Div, - } - } + mkbinop!(mul, *); + mkbinop!(add, +); + mkbinop!(sub, -); + mkbinop!(div, /); } #[derive(Clone, Debug, PartialEq)] -enum Opc { +pub enum Opc { Plus, Minus, Star, @@ -459,6 +236,7 @@ impl Opc { } } + // The precedence of the operator, higher means that operation has priority fn pred(&self) -> i32 { match self { Opc::Plus => 0, @@ -468,29 +246,31 @@ impl Opc { } } - fn code(&self, typ: Type) -> Operation { + // How one operator combines two values + fn apply(&self, l: Value, r: Value) -> Result { match self { - Opc::Plus => typ.add(), - Opc::Minus => typ.sub(), - Opc::Star => typ.mul(), - Opc::Slash => typ.div(), + Opc::Plus => l.add(r), + Opc::Minus => l.sub(r), + Opc::Star => l.mul(r), + Opc::Slash => l.div(r), } } } #[derive(Clone, Debug, PartialEq)] -enum Token { +pub enum Token { Eof, LParen, RParen, Operator(Opc), Symbol(String), - I32(u32), + I32(i32), F32(f32), Cast(Type), } -fn tokenize(input: &str) -> Result, &'static str> { +// A simple lexer +pub fn tokenize(input: &str) -> Result, &'static str> { let mut it = input.chars().peekable(); let mut v: Vec = Vec::new(); @@ -525,8 +305,8 @@ fn tokenize(input: &str) -> Result, &'static str> { Ok(f) => v.push(Token::F32(f)), } } else { - match n.parse::() { - Err(_) => return Err("How did we get here? (Invalid int format)"), + match n.parse::() { + Err(_) => return Err("Number too big"), Ok(n) => v.push(Token::I32(n)), } } @@ -540,68 +320,43 @@ fn tokenize(input: &str) -> Result, &'static str> { Ok(v) } -struct Parser { - output: ByteCode, - tokens: Vec, - index: usize, +// A recursive descent parser, see https://en.wikipedia.org/wiki/Recursive_descent_parser +pub struct Parser> { + tokens: Peekable, } -impl Parser { - fn new(toks: Vec) -> Self { +impl> Parser { + pub fn new(toks: I) -> Self { Self { - output: ByteCode(Vec::new()), - tokens: toks, - index: 0, + tokens: toks.peekable(), } } - fn add_op(&mut self, code: Operation) { - self.output.0.push(code as u32); - } - - fn add_32(&mut self, v: u32) { - self.output.0.push(v); + fn next(&mut self) -> Token { + self.tokens.next().unwrap_or(Token::Eof) } fn peek(&mut self) -> Token { - self.tokens - .get(self.index).cloned() - .unwrap_or(Token::Eof) - } - - fn eat(&mut self) { - self.index += 1 + self.tokens.peek().cloned().unwrap_or(Token::Eof) } - fn next(&mut self) -> Token { - let t = self.peek(); - self.eat(); - t - } - - fn parse(mut self) -> Result { + pub fn parse(mut self) -> Result { let p = self.primary()?; let e = self.expression(p, 0)?; match self.next() { - Token::Eof => { - match e { - Type::I32 => self.add_op(Operation::ExitI32), - Type::U32 => self.add_op(Operation::ExitU32), - Type::F32 => self.add_op(Operation::ExitF32), - } - Ok(self.output) - } + Token::Eof => Ok(e), _ => Err("Expected end of input"), } } - fn expression(&mut self, mut lhs: Type, pred: i32) -> Result { + // A pratt parser, see https://en.wikipedia.org/wiki/Operator-precedence_parser + fn expression(&mut self, mut lhs: Value, pred: i32) -> Result { loop { let op = match self.peek() { Token::Operator(op) if op.pred() >= pred => op, _ => break, }; - self.eat(); + self.next(); let mut rhs = self.primary()?; loop { @@ -612,16 +367,12 @@ impl Parser { rhs = self.expression(rhs, op2.pred())?; self.next(); } - if rhs != lhs { - return Err("Type mismatch"); - } - self.add_op(op.code(lhs)); - lhs = rhs; + lhs = op.apply(lhs, rhs)?; } Ok(lhs) } - fn primary(&mut self) -> Result { + fn primary(&mut self) -> Result { let r = match self.next() { Token::LParen => { let p = self.primary()?; @@ -631,35 +382,16 @@ impl Parser { _ => Err("Expected ')'")?, } } - Token::Operator(Opc::Minus) => { - let p = self.primary()?; - self.add_op(p.neg()); - p - } - Token::Operator(Opc::Plus) => { - - self.primary()? - } - Token::F32(n) => { - self.add_op(Operation::Const32); - self.add_32(n.to_bits()); - Type::F32 - } - Token::I32(n) => { - self.add_op(Operation::Const32); - self.add_32(n); - Type::I32 - } + Token::Operator(Opc::Minus) => self.primary()?.neg()?, + Token::Operator(Opc::Plus) => self.primary()?, + Token::F32(n) => Value::F32(n), + Token::I32(n) => Value::I32(n), _ => Err("Expected a value")?, }; Ok(match self.peek() { Token::Cast(t) => { - self.eat(); - match r.cast(t.clone()) { - Operation::Nop => (), - t => self.add_op(t), - } - t + self.next(); + r.cast(t)? } _ => r, }) From fb09e50dfa890fde8db2d72e3390896b306f5ef9 Mon Sep 17 00:00:00 2001 From: Aimar Ibarra Date: Sat, 4 May 2024 21:11:57 +0200 Subject: [PATCH 8/8] Separated math into a crate --- bismarck_commands/src/lib.rs | 1 - math/Cargo.toml | 4 ++++ math/src/lib.rs | 1 + {bismarck_commands => math}/src/math.rs | 0 4 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 math/Cargo.toml create mode 100644 math/src/lib.rs rename {bismarck_commands => math}/src/math.rs (100%) diff --git a/bismarck_commands/src/lib.rs b/bismarck_commands/src/lib.rs index 8e98249..6cd2d63 100644 --- a/bismarck_commands/src/lib.rs +++ b/bismarck_commands/src/lib.rs @@ -1,5 +1,4 @@ pub mod info; -pub mod math; pub mod moderation; pub mod neko; pub mod owner; diff --git a/math/Cargo.toml b/math/Cargo.toml new file mode 100644 index 0000000..a8aada7 --- /dev/null +++ b/math/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "math" +version = "0.1.0" +edition = "2024" \ No newline at end of file diff --git a/math/src/lib.rs b/math/src/lib.rs new file mode 100644 index 0000000..b8135c3 --- /dev/null +++ b/math/src/lib.rs @@ -0,0 +1 @@ +pub mod math; diff --git a/bismarck_commands/src/math.rs b/math/src/math.rs similarity index 100% rename from bismarck_commands/src/math.rs rename to math/src/math.rs