diff --git a/src/datapack/command/mod.rs b/src/datapack/command/mod.rs index c3170fb..2d0f715 100644 --- a/src/datapack/command/mod.rs +++ b/src/datapack/command/mod.rs @@ -10,7 +10,7 @@ use chksum_md5 as md5; use super::Function; use crate::{ prelude::Datapack, - util::compile::{CompileOptions, FunctionCompilerState, MutCompilerState}, + util::{compile::{CompileOptions, FunctionCompilerState, MutCompilerState}, MacroString}, }; /// Represents a command that can be included in a function. @@ -19,6 +19,8 @@ use crate::{ pub enum Command { /// A command that is already formatted as a string. Raw(String), + /// A command that contains macro usages + UsesMacro(MacroString), /// Message to be printed only in debug mode Debug(String), /// Execute command @@ -45,6 +47,7 @@ impl Command { ) -> Vec { match self { Self::Raw(command) => vec![command.clone()], + Self::UsesMacro(command) => vec![compile_macro(command)], Self::Debug(message) => compile_debug(message, options), Self::Execute(ex) => ex.compile(options, global_state, function_state), Self::Group(commands) => compile_group(commands, options, global_state, function_state), @@ -59,7 +62,8 @@ impl Command { // TODO: change comment to compile to `1`, make sure nothing breaks Self::Comment(_) => 0, Self::Debug(_) => usize::from(options.debug), - Self::Raw(cmd) => cmd.split('\n').count(), + Self::Raw(cmd) => cmd.lines().count(), + Self::UsesMacro(cmd) => cmd.line_count(), Self::Execute(ex) => ex.get_count(options), Self::Group(_) => 1, } @@ -71,6 +75,7 @@ impl Command { match self { Self::Comment(_) | Self::Debug(_) | Self::Group(_) => true, Self::Raw(cmd) => validate_raw_cmd(cmd, pack_formats), + Self::UsesMacro(cmd) => validate_raw_cmd(&cmd.compile(), pack_formats), Self::Execute(ex) => ex.validate(pack_formats), } } @@ -103,6 +108,14 @@ fn compile_debug(message: &str, option: &CompileOptions) -> Vec { } } +fn compile_macro(command: &MacroString) -> String { + if command.contains_macro() { + format!("${}", command.compile()) + } else { + command.compile() + } +} + #[tracing::instrument(skip_all, fields(commands = ?commands))] fn compile_group( commands: &[Command], diff --git a/src/util/macro_string.rs b/src/util/macro_string.rs new file mode 100644 index 0000000..c85c33a --- /dev/null +++ b/src/util/macro_string.rs @@ -0,0 +1,89 @@ +#![allow(clippy::module_name_repetitions)] + +use std::borrow::Cow; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum MacroString { + String(String), + MacroString(Vec), +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum MacroStringPart { + String(String), + MacroUsage(String), +} + +impl MacroString { + /// Returns whether the [`MacroString`] contains any macro usages + #[must_use] + pub fn contains_macro(&self) -> bool { + match self { + Self::String(_) => false, + Self::MacroString(parts) => !parts + .iter() + .all(|p| matches!(p, MacroStringPart::String(_))), + } + } + + /// Compiles to a string that Minecraft can interpret + #[must_use] + pub fn compile(&self) -> String { + match self { + Self::String(s) => s.to_owned(), + Self::MacroString(parts) => parts + .iter() + .map(|p| match p { + MacroStringPart::String(s) => Cow::Borrowed(s), + MacroStringPart::MacroUsage(m) => Cow::Owned(format!("$({m})")), + }) + .collect::>() + .iter() + .map(|p| p.as_str()) + .collect::>() + .join(""), + } + } + + /// Returns the amount of lines the string has + #[must_use] + pub fn line_count(&self) -> usize { + match self { + Self::String(s) => s.lines().count(), + Self::MacroString(parts) => { + parts + .iter() + .map(|p| match p { + MacroStringPart::String(s) => s.lines().count() - 1, + MacroStringPart::MacroUsage(_) => 0, + }) + .sum::() + + 1 + } + } + } +} + +impl From for MacroString { + fn from(value: String) -> Self { + Self::String(value) + } +} +impl From<&str> for MacroString { + fn from(value: &str) -> Self { + Self::String(value.to_string()) + } +} + +impl From for MacroStringPart { + fn from(value: String) -> Self { + Self::String(value) + } +} +impl From<&str> for MacroStringPart { + fn from(value: &str) -> Self { + Self::String(value.to_string()) + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index f8757c7..f684b10 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,7 +2,11 @@ pub mod compile; mod extendable_queue; +mod macro_string; pub(crate) mod pack_format; #[doc(inline)] pub use extendable_queue::ExtendableQueue; + +#[doc(inline)] +pub use macro_string::{MacroString, MacroStringPart};