Skip to content

Commit

Permalink
feat: commands
Browse files Browse the repository at this point in the history
  • Loading branch information
radstevee committed Dec 23, 2024
1 parent fa1aff7 commit b233a52
Show file tree
Hide file tree
Showing 13 changed files with 464 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"src/lib/adapters/anvil",
"src/lib/adapters/nbt",
"src/lib/adapters/nbt",
"src/lib/commands",
"src/lib/core",
"src/lib/core/state",
"src/lib/derive_macros",
Expand Down Expand Up @@ -86,6 +87,7 @@ codegen-units = 1
ferrumc-anvil = { path = "src/lib/adapters/anvil" }
ferrumc-config = { path = "src/lib/utils/config" }
ferrumc-core = { path = "src/lib/core" }
ferrumc-commands = { path = "src/lib/commands" }
ferrumc-ecs = { path = "src/lib/ecs" }
ferrumc-events = { path = "src/lib/events" }
ferrumc-general-purpose = { path = "src/lib/utils/general_purpose" }
Expand Down Expand Up @@ -181,5 +183,3 @@ memmap2 = "0.9.5"

# Benchmarking
criterion = { version = "0.5.1", features = ["html_reports"] }


16 changes: 16 additions & 0 deletions src/lib/commands/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "commands"
version = "0.1.0"
edition = "2021"

[dependencies]
thiserror = { workspace = true }
tracing = { workspace = true }
dashmap = { workspace = true }
tokio = { workspace = true }
ferrumc-text = { workspace = true }
ferrumc-state = { workspace = true }

[dev-dependencies] # Needed for the ServerState mock... :concern:
ferrumc-ecs = { workspace = true }
ferrumc-world = { workspace = true }
19 changes: 19 additions & 0 deletions src/lib/commands/src/arg/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use parser::ArgumentParser;

pub mod parser;

pub struct CommandArgument {
pub name: String,
pub required: bool,
pub parser: Box<dyn ArgumentParser>,
}

impl CommandArgument {
pub fn new(name: String, required: bool, parser: Box<dyn ArgumentParser>) -> Self {
CommandArgument {
name,
required,
parser,
}
}
}
25 changes: 25 additions & 0 deletions src/lib/commands/src/arg/parser/int.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::sync::{Arc, Mutex};

use crate::{ctx::CommandContext, input::CommandInput, ParserResult};

use super::{utils::error, ArgumentParser};

pub struct IntParser;

impl ArgumentParser for IntParser {
fn parse(&self, _ctx: Arc<&CommandContext>, input: Arc<Mutex<CommandInput>>) -> ParserResult {
let token = input.lock().unwrap().read_string();

match token.parse::<u32>() {
Ok(int) => Ok(Box::new(int)),
Err(err) => Err(error(err))
}
}

fn new() -> Self
where
Self: Sized,
{
IntParser
}
}
19 changes: 19 additions & 0 deletions src/lib/commands/src/arg/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::sync::{Arc, Mutex};

use crate::{ctx::CommandContext, input::CommandInput, ParserResult};

pub mod int;
pub mod string;

pub(crate) mod utils;

pub trait ArgumentParser: Send + Sync {
fn parse(
&self,
context: Arc<&CommandContext>,
input: Arc<Mutex<CommandInput>>,
) -> ParserResult;
fn new() -> Self
where
Self: Sized;
}
50 changes: 50 additions & 0 deletions src/lib/commands/src/arg/parser/string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::sync::{Arc, Mutex};

use crate::{ctx::CommandContext, input::CommandInput, ParserResult};

use super::ArgumentParser;

pub struct SingleStringParser;

impl ArgumentParser for SingleStringParser {
fn parse(&self, _ctx: Arc<&CommandContext>, input: Arc<Mutex<CommandInput>>) -> ParserResult {
Ok(Box::new(input.lock().unwrap().read_string()))
}

fn new() -> Self
where
Self: Sized,
{
SingleStringParser
}
}

pub struct GreedyStringParser;

impl ArgumentParser for GreedyStringParser {
fn parse(&self, _ctx: Arc<&CommandContext>, input: Arc<Mutex<CommandInput>>) -> ParserResult {
let mut result = String::new();

loop {
let token = input.lock().unwrap().read_string_skip_whitespace(false);

if token.is_empty() {
break;
}

if !result.is_empty() {
result.push(' ');
}
result.push_str(&token);
}

Ok(Box::new(result))
}

fn new() -> Self
where
Self: Sized,
{
GreedyStringParser
}
}
7 changes: 7 additions & 0 deletions src/lib/commands/src/arg/parser/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::error::Error;

use ferrumc_text::{NamedColor, TextComponent, TextComponentBuilder};

pub(crate) fn error(err: impl Error) -> TextComponent {
TextComponentBuilder::new(err.to_string()).color(NamedColor::Red).build()
}
43 changes: 43 additions & 0 deletions src/lib/commands/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::{
any::Any,
sync::{Arc, Mutex},
};

use ferrumc_state::GlobalState;

use crate::{input::CommandInput, Command};

pub struct CommandContext {
pub input: Arc<Mutex<CommandInput>>,
pub command: Arc<Command>,
pub state: GlobalState,
}

impl CommandContext {
pub fn new(input: CommandInput, command: Arc<Command>, state: GlobalState) -> Arc<Self> {
Arc::new(Self {
input: Arc::new(Mutex::new(input)),
command,
state,
})
}

pub fn arg<T: Any>(&self, name: &str) -> T {
if let Some(arg) = self.command.args.iter().find(|a| a.name == name) {
let input = self.input.clone();
let result = arg.parser.parse(Arc::new(self), input);

return match result {
Ok(b) => match b.downcast::<T>() {
Ok(value) => *value,
Err(_) => {
todo!("failed downcasting command argument, change design of this fn");
}
},
Err(_) => unreachable!("arg has already been validated")
};
} else {
todo!();
}
}
}
7 changes: 7 additions & 0 deletions src/lib/commands/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use thiserror::Error;

#[derive(Debug, Clone, Error)]
pub enum CommandError {
#[error("Something failed lol")]
SomeError,
}
34 changes: 34 additions & 0 deletions src/lib/commands/src/infrastructure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use dashmap::DashMap;
use std::sync::{Arc, LazyLock};

use crate::Command;

static COMMANDS: LazyLock<DashMap<&'static str, Arc<Command>>> = LazyLock::new(DashMap::new);

pub fn register_command(command: Arc<Command>) {
COMMANDS.insert(command.name, command);
}

pub fn get_command_by_name(name: &'static str) -> Option<Arc<Command>> {
COMMANDS.get(name).map(|cmd_ref| Arc::clone(&cmd_ref))
}

pub fn find_command(input: &'static str) -> Option<Arc<Command>> {
let mut command = None;
let mut input = input;

while !input.is_empty() {
command = get_command_by_name(input);
if command.is_some() {
break;
}

if let Some(pos) = input.rfind(' ') {
input = &input[..pos];
} else {
input = "";
}
}

command
}
109 changes: 109 additions & 0 deletions src/lib/commands/src/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/// Very based on Cloud, this is gonna have to be changed up a bit probably.
#[derive(Clone)]
pub struct CommandInput {
pub input: String,
pub cursor: u32,
}

impl CommandInput {
pub fn of(string: String) -> Self {
Self {
input: string,
cursor: 0,
}
}
pub fn append_string(&mut self, string: String) {
self.input += &*string;
}
pub fn move_cursor(&mut self, chars: u32) {
if self.cursor + chars > self.input.len() as u32 {
return;
}

self.cursor += chars;
}
pub fn remaining_length(&self) -> u32 {
self.input.len() as u32 - self.cursor
}
pub fn peek(&self) -> Option<char> {
self.input.chars().nth(self.cursor as usize)
}
pub fn has_remaining_input(&self) -> bool {
self.cursor < self.input.len() as u32
}
pub fn skip_whitespace(&mut self, max_spaces: u32, preserve_single: bool) {
if preserve_single && self.remaining_length() == 1 && self.peek() == Some(' ') {
return;
}

let mut i = 0;
while i < max_spaces
&& self.has_remaining_input()
&& self.peek().map_or(false, |c| c.is_whitespace())
{
self.read(1);
i += 1;
}
}
pub fn remaining_input(&self) -> String {
self.input[..self.cursor as usize].to_string()
}
pub fn peek_string_chars(&self, chars: u32) -> String {
let remaining = self.remaining_input();
if chars > remaining.len() as u32 {
return "".to_string();
}

remaining[0..chars as usize].to_string()
}
pub fn read(&mut self, chars: u32) -> String {
let read_string = self.peek_string_chars(chars);
self.move_cursor(chars);
read_string
}
pub fn remaining_tokens(&self) -> u32 {
let count = self.remaining_input().split(' ').count() as u32;
if self.remaining_input().ends_with(' ') {
return count + 1;
}
count
}
pub fn read_string(&mut self) -> String {
self.skip_whitespace(u32::MAX, false);
let mut result = String::new();
while let Some(c) = self.peek() {
if c.is_whitespace() {
break;
}
result.push(c);
self.move_cursor(1);
}
result
}
pub fn peek_string(&self) -> String {
let remaining = self.remaining_input();
remaining
.split_whitespace()
.next()
.unwrap_or("")
.to_string()
}
pub fn read_until(&mut self, separator: char) -> String {
self.skip_whitespace(u32::MAX, false);
let mut result = String::new();
while let Some(c) = self.peek() {
if c == separator {
self.move_cursor(1);
break;
}
result.push(c);
self.move_cursor(1);
}
result
}
pub fn read_string_skip_whitespace(&mut self, preserve_single: bool) -> String {
let read_string = self.read_string();
self.skip_whitespace(u32::MAX, preserve_single);
read_string
}
}
Loading

0 comments on commit b233a52

Please sign in to comment.