add while command
This commit is contained in:
parent
d4689c696a
commit
d9608a8f8c
|
@ -11,10 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- support for commands using macros
|
||||
- support for registering scoreboards (automatic creation and deletion)
|
||||
- "return" command with special handling in groups and conditionals
|
||||
- while loop command
|
||||
- `CommandCollection` trait for common operations on collections of commands (e.g. `Vec<Command>`)
|
||||
|
||||
### Changed
|
||||
- use "return" command for conditionals instead of data storage when using supported pack format
|
||||
- update latest datapack format to 61
|
||||
- update latest datapack format to 81
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ fn compile_using_data_storage(
|
|||
global_state: &MutCompilerState,
|
||||
function_state: &FunctionCompilerState,
|
||||
) -> Vec<CompiledCommand> {
|
||||
let contains_macro = prefix_contains_macros || cond.contains_macro();
|
||||
let contains_macro = prefix_contains_macros || cond.contains_macros();
|
||||
let then_count = then.get_count(options);
|
||||
|
||||
let str_cond = cond.clone().compile(options, global_state, function_state);
|
||||
|
@ -208,7 +208,7 @@ fn compile_since_20_format(
|
|||
global_state: &MutCompilerState,
|
||||
function_state: &FunctionCompilerState,
|
||||
) -> Vec<CompiledCommand> {
|
||||
let contains_macros = prefix_contains_macros || cond.contains_macro();
|
||||
let contains_macros = prefix_contains_macros || cond.contains_macros();
|
||||
let then_count = then.get_count(options);
|
||||
|
||||
let str_cond = cond
|
||||
|
@ -321,7 +321,7 @@ fn combine_conditions_commands_concat(
|
|||
conditions
|
||||
.into_iter()
|
||||
.map(|cond| {
|
||||
let prefix = if cond.contains_macro() {
|
||||
let prefix = if cond.contains_macros() {
|
||||
Command::UsesMacro(cond + " ")
|
||||
} else {
|
||||
Command::Raw(cond.compile() + " ")
|
||||
|
@ -474,6 +474,15 @@ impl Condition {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_count(&self) -> usize {
|
||||
match self.normalize() {
|
||||
Self::Atom(_) | Self::Not(_) => 1,
|
||||
Self::Or(a, b) => a.get_count() + b.get_count(),
|
||||
Self::And(a, b) => a.get_count() * b.get_count(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the condition into a [`MacroString`].
|
||||
///
|
||||
/// Will fail if the condition contains an `Or` or double nested `Not` variant. Use `compile` instead.
|
||||
|
@ -527,11 +536,11 @@ impl Condition {
|
|||
|
||||
/// Check whether the condition contains a macro.
|
||||
#[must_use]
|
||||
pub fn contains_macro(&self) -> bool {
|
||||
pub fn contains_macros(&self) -> bool {
|
||||
match self {
|
||||
Self::Atom(s) => s.contains_macro(),
|
||||
Self::Not(n) => n.contains_macro(),
|
||||
Self::And(a, b) | Self::Or(a, b) => a.contains_macro() || b.contains_macro(),
|
||||
Self::Atom(s) => s.contains_macros(),
|
||||
Self::Not(n) => n.contains_macros(),
|
||||
Self::And(a, b) | Self::Or(a, b) => a.contains_macros() || b.contains_macros(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ impl Execute {
|
|||
arg = arg.compile()
|
||||
),
|
||||
require_grouping,
|
||||
prefix_contains_macros || arg.contains_macro(),
|
||||
prefix_contains_macros || arg.contains_macros(),
|
||||
options,
|
||||
global_state,
|
||||
function_state,
|
||||
|
@ -102,7 +102,7 @@ impl Execute {
|
|||
selector = selector.compile()
|
||||
),
|
||||
require_grouping,
|
||||
prefix_contains_macros || selector.contains_macro(),
|
||||
prefix_contains_macros || selector.contains_macros(),
|
||||
options,
|
||||
global_state,
|
||||
function_state,
|
||||
|
@ -124,7 +124,7 @@ impl Execute {
|
|||
arg = arg.compile()
|
||||
),
|
||||
true,
|
||||
prefix_contains_macros || arg.contains_macro(),
|
||||
prefix_contains_macros || arg.contains_macros(),
|
||||
options,
|
||||
global_state,
|
||||
function_state,
|
||||
|
@ -247,7 +247,7 @@ impl Execute {
|
|||
|
||||
/// Check whether the execute command contains a macro.
|
||||
#[must_use]
|
||||
pub fn contains_macro(&self) -> bool {
|
||||
pub fn contains_macros(&self) -> bool {
|
||||
match self {
|
||||
Self::Facing(s, next)
|
||||
| Self::Store(s, next)
|
||||
|
@ -260,14 +260,14 @@ impl Execute {
|
|||
| Self::Align(s, next)
|
||||
| Self::Anchored(s, next)
|
||||
| Self::Summon(s, next)
|
||||
| Self::On(s, next) => s.contains_macro() || next.contains_macro(),
|
||||
| Self::On(s, next) => s.contains_macros() || next.contains_macros(),
|
||||
Self::If(cond, then, el) => {
|
||||
cond.contains_macro()
|
||||
|| then.contains_macro()
|
||||
|| el.as_deref().is_some_and(Self::contains_macro)
|
||||
cond.contains_macros()
|
||||
|| then.contains_macros()
|
||||
|| el.as_deref().is_some_and(Self::contains_macros)
|
||||
}
|
||||
Self::Run(cmd) => cmd.contains_macro(),
|
||||
Self::Runs(cmds) => cmds.iter().any(super::Command::contains_macro),
|
||||
Self::Run(cmd) => cmd.contains_macros(),
|
||||
Self::Runs(cmds) => cmds.iter().any(super::Command::contains_macros),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ use crate::{
|
|||
prelude::Datapack,
|
||||
util::{
|
||||
compile::{CompileOptions, CompiledCommand, FunctionCompilerState, MutCompilerState},
|
||||
MacroString,
|
||||
CommandCollection, MacroString, MacroStringPart,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -39,6 +39,8 @@ pub enum Command {
|
|||
Comment(String),
|
||||
/// Return value
|
||||
Return(ReturnCommand),
|
||||
/// While loop
|
||||
While(While),
|
||||
|
||||
/// Command that is a concatenation of two commands
|
||||
Concat(Box<Command>, Box<Command>),
|
||||
|
@ -70,6 +72,8 @@ impl Command {
|
|||
vec![CompiledCommand::new("#".to_string() + comment).with_forbid_prefix(true)]
|
||||
}
|
||||
Self::Return(return_cmd) => return_cmd.compile(options, global_state, function_state),
|
||||
Self::While(while_cmd) => while_cmd.compile(options, global_state, function_state),
|
||||
|
||||
Self::Concat(a, b) => {
|
||||
let a = a.compile(options, global_state, function_state);
|
||||
let b = b.compile(options, global_state, function_state);
|
||||
|
@ -104,6 +108,8 @@ impl Command {
|
|||
Self::Execute(ex) => ex.get_count(options),
|
||||
Self::Group(group) => group.get_count(options),
|
||||
Self::Return(_) => 1,
|
||||
Self::While(while_cmd) => while_cmd.get_count(options),
|
||||
|
||||
Self::Concat(a, b) => a.get_count(options) + b.get_count(options) - 1,
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +118,7 @@ impl Command {
|
|||
#[must_use]
|
||||
pub fn validate(&self, pack_formats: &RangeInclusive<u8>) -> bool {
|
||||
let command_valid = match self {
|
||||
Self::Comment(_) | Self::Debug(_) | Self::Group(_) => true,
|
||||
Self::Comment(_) | Self::Debug(_) | Self::Group(_) | Self::While(_) => 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),
|
||||
|
@ -122,10 +128,11 @@ impl Command {
|
|||
pack_formats.start() >= &16 && cmd.validate(pack_formats)
|
||||
}
|
||||
},
|
||||
|
||||
Self::Concat(a, b) => a.validate(pack_formats) && b.validate(pack_formats),
|
||||
};
|
||||
if pack_formats.start() < &16 {
|
||||
command_valid && !self.contains_macro()
|
||||
command_valid && !self.contains_macros()
|
||||
} else {
|
||||
command_valid
|
||||
}
|
||||
|
@ -133,17 +140,19 @@ impl Command {
|
|||
|
||||
/// Check whether the command contains a macro.
|
||||
#[must_use]
|
||||
pub fn contains_macro(&self) -> bool {
|
||||
pub fn contains_macros(&self) -> bool {
|
||||
match self {
|
||||
Self::Raw(_) | Self::Comment(_) => false,
|
||||
Self::UsesMacro(s) | Self::Debug(s) => s.contains_macro(),
|
||||
Self::Group(group) => group.contains_macro(),
|
||||
Self::Execute(ex) => ex.contains_macro(),
|
||||
Self::UsesMacro(s) | Self::Debug(s) => s.contains_macros(),
|
||||
Self::Group(group) => group.contains_macros(),
|
||||
Self::Execute(ex) => ex.contains_macros(),
|
||||
Self::Return(ret) => match ret {
|
||||
ReturnCommand::Value(value) => value.contains_macro(),
|
||||
ReturnCommand::Command(cmd) => cmd.contains_macro(),
|
||||
ReturnCommand::Value(value) => value.contains_macros(),
|
||||
ReturnCommand::Command(cmd) => cmd.contains_macros(),
|
||||
},
|
||||
Self::Concat(a, b) => a.contains_macro() || b.contains_macro(),
|
||||
Self::While(while_cmd) => while_cmd.contains_macros(),
|
||||
|
||||
Self::Concat(a, b) => a.contains_macros() || b.contains_macros(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,6 +168,8 @@ impl Command {
|
|||
ReturnCommand::Value(value) => value.get_macros(),
|
||||
ReturnCommand::Command(cmd) => cmd.get_macros(),
|
||||
},
|
||||
Self::While(while_cmd) => while_cmd.get_macros(),
|
||||
|
||||
Self::Concat(a, b) => {
|
||||
let mut macros = a.get_macros();
|
||||
macros.extend(b.get_macros());
|
||||
|
@ -172,12 +183,17 @@ impl Command {
|
|||
pub fn forbid_prefix(&self) -> bool {
|
||||
match self {
|
||||
Self::Comment(_) => true,
|
||||
Self::Raw(_) | Self::Debug(_) | Self::Execute(_) | Self::UsesMacro(_) => false,
|
||||
Self::Raw(_)
|
||||
| Self::Debug(_)
|
||||
| Self::Execute(_)
|
||||
| Self::UsesMacro(_)
|
||||
| Self::While(_) => false,
|
||||
Self::Group(group) => group.forbid_prefix(),
|
||||
Self::Return(ret) => match ret {
|
||||
ReturnCommand::Value(_) => false,
|
||||
ReturnCommand::Command(cmd) => cmd.forbid_prefix(),
|
||||
},
|
||||
|
||||
Self::Concat(a, _) => a.forbid_prefix(),
|
||||
}
|
||||
}
|
||||
|
@ -188,11 +204,13 @@ impl Command {
|
|||
match self {
|
||||
Self::Comment(_) | Self::Debug(_) => false,
|
||||
Self::Return(_) => true,
|
||||
Self::Concat(a, b) => a.contains_return() || b.contains_return(),
|
||||
Self::Execute(exec) => exec.contains_return(),
|
||||
Self::Raw(cmd) => cmd.starts_with("return "),
|
||||
Self::UsesMacro(m) => m.compile().starts_with("return "),
|
||||
Self::Group(g) => g.contains_return(),
|
||||
Self::While(w) => w.contains_return(),
|
||||
|
||||
Self::Concat(a, b) => a.contains_return() || b.contains_return(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +231,24 @@ impl From<&mut Function> for Command {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ReturnCommand> for Command {
|
||||
fn from(value: ReturnCommand) -> Self {
|
||||
Self::Return(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Group> for Command {
|
||||
fn from(value: Group) -> Self {
|
||||
Self::Group(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<While> for Command {
|
||||
fn from(value: While) -> Self {
|
||||
Self::While(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a group of commands to be executed in sequence.
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -279,25 +315,40 @@ impl Group {
|
|||
self.commands[0].compile(options, global_state, function_state)
|
||||
}
|
||||
_ => {
|
||||
let pass_macros = self.contains_macro();
|
||||
let contains_return = self.contains_return();
|
||||
let commands = &self.commands;
|
||||
let block_pass_macros = self.block_pass_macros.as_ref();
|
||||
let macro_data_storage_name = self.data_storage_name.as_deref();
|
||||
|
||||
// calculate a hashed path for the function in the `sb` subfolder
|
||||
let function_path = Self::generate_function_path(function_state);
|
||||
let function_path = generate_group_function_path(function_state);
|
||||
|
||||
let namespace = function_state.namespace();
|
||||
|
||||
// create a new function with the commands
|
||||
let mut function = Function::new(namespace, &function_path);
|
||||
function
|
||||
.get_commands_mut()
|
||||
.extend(self.commands.iter().cloned());
|
||||
function_state.add_function(&function_path, function);
|
||||
let contained_macros = {
|
||||
let mut macros = HashSet::new();
|
||||
for cmd in commands {
|
||||
macros.extend(cmd.get_macros());
|
||||
}
|
||||
macros
|
||||
};
|
||||
let (prepare_data_storage, function_invocation) = get_group_invocation_commands(
|
||||
&function_path,
|
||||
namespace,
|
||||
commands,
|
||||
&contained_macros,
|
||||
block_pass_macros,
|
||||
macro_data_storage_name,
|
||||
);
|
||||
|
||||
let mut function_invocation = format!("function {namespace}:{function_path}");
|
||||
create_group_function(&function_path, commands, function_state);
|
||||
|
||||
let contains_return = commands.contains_return();
|
||||
|
||||
let additional_return_cmds = if contains_return {
|
||||
let full_path = format!("{namespace}:{function_path}");
|
||||
let full_path = format!(
|
||||
"{namespace}:{function_path}",
|
||||
namespace = function_state.namespace()
|
||||
);
|
||||
let return_data_path = md5::hash(&full_path).to_hex_lowercase();
|
||||
|
||||
let pre_cmds = Command::Raw(format!(
|
||||
|
@ -312,23 +363,23 @@ impl Group {
|
|||
);
|
||||
|
||||
let post_cmd_store = global_state
|
||||
.read()
|
||||
.unwrap()
|
||||
.functions_with_special_return
|
||||
.get(&format!(
|
||||
"{}:{}",
|
||||
function_state.namespace(),
|
||||
function_state.path()
|
||||
))
|
||||
.cloned().map(|parent_return_data_path| {
|
||||
Command::Execute(Execute::If(
|
||||
post_condition.clone(),
|
||||
Box::new(Execute::Run(Box::new(Command::Raw(format!(
|
||||
"data modify storage shulkerbox:return {parent_return_data_path} set from storage shulkerbox:return {return_data_path}"
|
||||
))))),
|
||||
None,
|
||||
))
|
||||
});
|
||||
.read()
|
||||
.unwrap()
|
||||
.functions_with_special_return
|
||||
.get(&format!(
|
||||
"{}:{}",
|
||||
function_state.namespace(),
|
||||
function_state.path()
|
||||
))
|
||||
.cloned().map(|parent_return_data_path| {
|
||||
Command::Execute(Execute::If(
|
||||
post_condition.clone(),
|
||||
Box::new(Execute::Run(Box::new(Command::Raw(format!(
|
||||
"data modify storage shulkerbox:return {parent_return_data_path} set from storage shulkerbox:return {return_data_path}"
|
||||
))))),
|
||||
None,
|
||||
))
|
||||
});
|
||||
|
||||
let post_cmd_return = Command::Execute(Execute::If(
|
||||
post_condition,
|
||||
|
@ -356,72 +407,26 @@ impl Group {
|
|||
None
|
||||
};
|
||||
|
||||
let prepare_data_storage = if pass_macros {
|
||||
let contained_macros = self.get_macros();
|
||||
let not_all_macros_blocked = self
|
||||
.block_pass_macros
|
||||
.as_ref()
|
||||
.is_none_or(|b| contained_macros.iter().any(|&m| !b.contains(m)));
|
||||
|
||||
if !contained_macros.is_empty()
|
||||
&& (self.data_storage_name.is_some() || not_all_macros_blocked)
|
||||
{
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// WARNING: this seems to be the only way to pass macros to the function called.
|
||||
// Because everything is passed as a string, it looses one "level" of escaping per pass.
|
||||
let macros_block = self
|
||||
.get_macros()
|
||||
.into_iter()
|
||||
.filter(|&m| {
|
||||
self.block_pass_macros
|
||||
.as_ref()
|
||||
.is_none_or(|b| !b.contains(m))
|
||||
})
|
||||
.map(|m| format!(r#"{m}:"$({m})""#))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
if let Some(data_storage_name) = self.data_storage_name.as_deref() {
|
||||
let _ =
|
||||
write!(function_invocation, " with storage {data_storage_name}");
|
||||
|
||||
not_all_macros_blocked.then(|| {
|
||||
CompiledCommand::new(format!(
|
||||
"data merge storage {data_storage_name} {{{macros_block}}}"
|
||||
))
|
||||
.with_contains_macros(true)
|
||||
})
|
||||
} else {
|
||||
let _ = write!(function_invocation, " {{{macros_block}}}");
|
||||
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((mut pre_cmds, post_cmds)) = additional_return_cmds {
|
||||
if let Some(prepare_datastorage_cmd) = prepare_data_storage {
|
||||
pre_cmds.push(prepare_datastorage_cmd);
|
||||
pre_cmds.extend(prepare_datastorage_cmd.compile(
|
||||
options,
|
||||
global_state,
|
||||
function_state,
|
||||
));
|
||||
}
|
||||
pre_cmds.push(
|
||||
CompiledCommand::new(function_invocation)
|
||||
.with_contains_macros(pass_macros && self.data_storage_name.is_none()),
|
||||
);
|
||||
pre_cmds.extend(function_invocation.compile(
|
||||
options,
|
||||
global_state,
|
||||
function_state,
|
||||
));
|
||||
pre_cmds.extend(post_cmds);
|
||||
pre_cmds
|
||||
} else {
|
||||
prepare_data_storage
|
||||
.into_iter()
|
||||
.chain(std::iter::once(
|
||||
CompiledCommand::new(function_invocation).with_contains_macros(
|
||||
pass_macros && self.data_storage_name.is_none(),
|
||||
),
|
||||
))
|
||||
.flat_map(|prep| prep.compile(options, global_state, function_state))
|
||||
.chain(function_invocation.compile(options, global_state, function_state))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
@ -460,27 +465,14 @@ impl Group {
|
|||
|
||||
/// Check whether the group contains a macro.
|
||||
#[must_use]
|
||||
pub fn contains_macro(&self) -> bool {
|
||||
self.commands.iter().any(Command::contains_macro)
|
||||
pub fn contains_macros(&self) -> bool {
|
||||
self.commands.contains_macros()
|
||||
}
|
||||
|
||||
/// Check whether the group contains a return command.
|
||||
#[must_use]
|
||||
pub fn contains_return(&self) -> bool {
|
||||
self.commands.iter().any(Command::contains_return)
|
||||
}
|
||||
|
||||
/// Generate a unique function path based on the function state.
|
||||
#[must_use]
|
||||
fn generate_function_path(function_state: &FunctionCompilerState) -> String {
|
||||
let uid = function_state.request_uid();
|
||||
let function_path = function_state.path();
|
||||
let function_path = function_path.strip_prefix("sb/").unwrap_or(function_path);
|
||||
|
||||
let pre_hash_path = function_path.to_owned() + ":" + &uid.to_string();
|
||||
let hash = md5::hash(pre_hash_path).to_hex_lowercase();
|
||||
|
||||
"sb/".to_string() + function_path + "/" + &hash[..16]
|
||||
self.commands.contains_return()
|
||||
}
|
||||
|
||||
/// Returns the names of the macros used
|
||||
|
@ -512,7 +504,7 @@ impl Group {
|
|||
0 if !self.always_create_function => 0,
|
||||
1 if !self.always_create_function => 1,
|
||||
_ => {
|
||||
let pass_macros = self.contains_macro();
|
||||
let pass_macros = self.contains_macros();
|
||||
let contains_return = self.contains_return();
|
||||
|
||||
let additional_return_cmds = if contains_return {
|
||||
|
@ -575,6 +567,101 @@ impl Hash for Group {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_group_invocation_commands(
|
||||
function_path: &str,
|
||||
namespace: &str,
|
||||
commands: &[Command],
|
||||
contained_macros: &HashSet<&str>,
|
||||
block_pass_macros: Option<&HashSet<String>>,
|
||||
macro_data_storage_name: Option<&str>,
|
||||
) -> (Option<Command>, Command) {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let mut function_invocation = format!("function {namespace}:{function_path}");
|
||||
|
||||
if commands.contains_macros() {
|
||||
// WARNING: this seems to be the only way to pass macros to the function called.
|
||||
// Because everything is passed as a string, it looses one "level" of escaping per pass.
|
||||
let mut macros_parts = contained_macros
|
||||
.iter()
|
||||
.filter(|&&m| block_pass_macros.is_none_or(|b| !b.contains(m)))
|
||||
.flat_map(|&m| {
|
||||
vec![
|
||||
MacroStringPart::String(format!(r#"{m}:""#)),
|
||||
MacroStringPart::MacroUsage(m.to_string()),
|
||||
MacroStringPart::String(r#"""#.to_string()),
|
||||
]
|
||||
})
|
||||
.fold(Vec::new(), |mut acc, part| {
|
||||
if !acc.is_empty() {
|
||||
acc.push(MacroStringPart::String(",".to_string()));
|
||||
}
|
||||
|
||||
acc.push(part);
|
||||
|
||||
acc
|
||||
});
|
||||
|
||||
if let Some(data_storage_name) = macro_data_storage_name {
|
||||
let _ = write!(function_invocation, " with storage {data_storage_name}");
|
||||
|
||||
let not_all_macros_blocked =
|
||||
block_pass_macros.is_none_or(|b| contained_macros.iter().any(|&m| !b.contains(m)));
|
||||
|
||||
if not_all_macros_blocked {
|
||||
macros_parts.insert(
|
||||
0,
|
||||
MacroStringPart::String(format!("data merge storage {data_storage_name} {{")),
|
||||
);
|
||||
macros_parts.push(MacroStringPart::String("}".to_string()));
|
||||
let macro_string = MacroString::MacroString(macros_parts).normalize();
|
||||
|
||||
(
|
||||
Some(Command::UsesMacro(macro_string)),
|
||||
Command::Raw(function_invocation),
|
||||
)
|
||||
} else {
|
||||
(None, Command::Raw(function_invocation))
|
||||
}
|
||||
} else {
|
||||
let _ = write!(function_invocation, " {{");
|
||||
macros_parts.insert(0, MacroStringPart::String(function_invocation));
|
||||
macros_parts.push(MacroStringPart::String("}".to_string()));
|
||||
|
||||
let macro_string = MacroString::MacroString(macros_parts).normalize();
|
||||
|
||||
(None, Command::UsesMacro(macro_string))
|
||||
}
|
||||
} else {
|
||||
(None, Command::Raw(function_invocation))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_group_function(
|
||||
function_path: &str,
|
||||
commands: &[Command],
|
||||
function_state: &FunctionCompilerState,
|
||||
) {
|
||||
let namespace = function_state.namespace();
|
||||
|
||||
// create a new function with the commands
|
||||
let mut function = Function::new(namespace, function_path);
|
||||
function.get_commands_mut().extend(commands.iter().cloned());
|
||||
function_state.add_function(function_path, function);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn generate_group_function_path(function_state: &FunctionCompilerState) -> String {
|
||||
let uid = function_state.request_uid();
|
||||
let function_path = function_state.path();
|
||||
let function_path = function_path.strip_prefix("sb/").unwrap_or(function_path);
|
||||
|
||||
let pre_hash_path = function_path.to_owned() + ":" + &uid.to_string();
|
||||
let hash = md5::hash(pre_hash_path).to_hex_lowercase();
|
||||
|
||||
"sb/".to_string() + function_path + "/" + &hash[..16]
|
||||
}
|
||||
|
||||
/// Represents a command that returns a value.
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -658,6 +745,104 @@ impl ReturnCommand {
|
|||
}
|
||||
}
|
||||
|
||||
/// Loops the commands while a condition is true.
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct While {
|
||||
condition: Condition,
|
||||
commands: Vec<Command>,
|
||||
}
|
||||
|
||||
impl While {
|
||||
pub fn new(condition: impl Into<Condition>, commands: Vec<Command>) -> Self {
|
||||
Self {
|
||||
condition: condition.into(),
|
||||
commands,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(
|
||||
&self,
|
||||
options: &CompileOptions,
|
||||
global_state: &MutCompilerState,
|
||||
function_state: &FunctionCompilerState,
|
||||
) -> Vec<CompiledCommand> {
|
||||
// calculate a hashed path for the function in the `sb` subfolder
|
||||
let function_path = generate_group_function_path(function_state);
|
||||
|
||||
let namespace = function_state.namespace();
|
||||
|
||||
let contained_macros = {
|
||||
let mut macros = HashSet::new();
|
||||
for cmd in &self.commands {
|
||||
macros.extend(cmd.get_macros());
|
||||
}
|
||||
macros
|
||||
};
|
||||
let (prepare_data_storage, function_invocation) = get_group_invocation_commands(
|
||||
&function_path,
|
||||
namespace,
|
||||
&self.commands,
|
||||
&contained_macros,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let execute_tail = if let Some(prepare_datastorage_cmd) = prepare_data_storage {
|
||||
Execute::Runs(vec![prepare_datastorage_cmd, function_invocation])
|
||||
} else {
|
||||
Execute::Run(Box::new(function_invocation))
|
||||
};
|
||||
let conditional_run_cmd = Command::Execute(Execute::If(
|
||||
self.condition.clone(),
|
||||
Box::new(execute_tail),
|
||||
None,
|
||||
));
|
||||
|
||||
let mut commands = self.commands.clone();
|
||||
commands.push(conditional_run_cmd.clone());
|
||||
|
||||
create_group_function(&function_path, &commands, function_state);
|
||||
|
||||
let contains_return = commands.contains_return();
|
||||
|
||||
if contains_return {
|
||||
todo!("While loops with return commands are not yet supported");
|
||||
}
|
||||
|
||||
conditional_run_cmd.compile(options, global_state, function_state)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_count(&self, options: &CompileOptions) -> usize {
|
||||
Execute::If(
|
||||
self.condition.clone(),
|
||||
Box::new(Execute::Run(Box::new(Command::Raw(String::new())))),
|
||||
None,
|
||||
)
|
||||
.get_count(options)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn contains_macros(&self) -> bool {
|
||||
self.condition.contains_macros() || self.commands.contains_macros()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_macros(&self) -> HashSet<&str> {
|
||||
let mut macros = self.condition.get_macros();
|
||||
for cmd in &self.commands {
|
||||
macros.extend(cmd.get_macros());
|
||||
}
|
||||
macros
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn contains_return(&self) -> bool {
|
||||
self.commands.contains_return()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn validate_raw_cmd(cmd: &str, pack_formats: &RangeInclusive<u8>) -> bool {
|
||||
static CMD_FORMATS: LazyLock<HashMap<&str, RangeInclusive<u8>>> = LazyLock::new(|| {
|
||||
|
|
|
@ -65,7 +65,7 @@ impl Function {
|
|||
.flat_map(|c| {
|
||||
let cmds = c.compile(options, global_state, function_state);
|
||||
|
||||
if c.contains_macro() {
|
||||
if c.contains_macros() {
|
||||
cmds.into_iter()
|
||||
.map(|c| {
|
||||
if c.contains_macros() {
|
||||
|
|
|
@ -4,7 +4,7 @@ mod command;
|
|||
mod function;
|
||||
mod namespace;
|
||||
pub mod tag;
|
||||
pub use command::{Command, Condition, Execute, Group, ReturnCommand};
|
||||
pub use command::{Command, Condition, Execute, Group, ReturnCommand, While};
|
||||
pub use function::Function;
|
||||
pub use namespace::Namespace;
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use crate::prelude::Command;
|
||||
|
||||
/// A trait for collections that can hold `Command` items.
|
||||
pub trait CommandCollection {
|
||||
/// Returns an iterator over the commands in the collection.
|
||||
fn commands(&self) -> impl Iterator<Item = &Command>;
|
||||
|
||||
/// Checks if any command in the collection contains macros.
|
||||
fn contains_macros(&self) -> bool {
|
||||
self.commands().any(Command::contains_macros)
|
||||
}
|
||||
|
||||
/// Returns a set of all macro names used in the commands of the collection.
|
||||
fn get_macros(&self) -> HashSet<&str> {
|
||||
self.commands()
|
||||
.flat_map(Command::get_macros)
|
||||
.collect::<HashSet<&str>>()
|
||||
}
|
||||
|
||||
/// Checks if any command in the collection is a return command.
|
||||
fn contains_return(&self) -> bool {
|
||||
self.commands().any(Command::contains_return)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> CommandCollection for C
|
||||
where
|
||||
C: AsRef<[Command]>,
|
||||
{
|
||||
fn commands(&self) -> impl Iterator<Item = &Command> {
|
||||
self.as_ref().iter()
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ pub enum MacroStringPart {
|
|||
impl MacroString {
|
||||
/// Returns whether the [`MacroString`] contains any macro usages
|
||||
#[must_use]
|
||||
pub fn contains_macro(&self) -> bool {
|
||||
pub fn contains_macros(&self) -> bool {
|
||||
match self {
|
||||
Self::String(_) => false,
|
||||
Self::MacroString(parts) => !parts
|
||||
|
@ -47,6 +47,40 @@ impl MacroString {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn normalize(self) -> Self {
|
||||
match self {
|
||||
Self::String(_) => self,
|
||||
Self::MacroString(parts) => {
|
||||
let mut normalized_parts = Vec::new();
|
||||
|
||||
for part in parts {
|
||||
match part {
|
||||
MacroStringPart::String(s) => {
|
||||
if let Some(MacroStringPart::String(last)) = normalized_parts.last_mut()
|
||||
{
|
||||
last.push_str(&s);
|
||||
} else if !s.is_empty() {
|
||||
normalized_parts.push(MacroStringPart::String(s));
|
||||
}
|
||||
}
|
||||
MacroStringPart::MacroUsage(m) => {
|
||||
normalized_parts.push(MacroStringPart::MacroUsage(m));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if normalized_parts.len() == 1 {
|
||||
if let MacroStringPart::String(s) = &normalized_parts[0] {
|
||||
return Self::String(s.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Self::MacroString(normalized_parts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the amount of lines the string has
|
||||
#[must_use]
|
||||
pub fn line_count(&self) -> usize {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Utility functions for the Shulkerbox project.
|
||||
|
||||
mod command_collection;
|
||||
pub mod compile;
|
||||
mod extendable_queue;
|
||||
mod macro_string;
|
||||
|
@ -10,3 +11,6 @@ pub use extendable_queue::ExtendableQueue;
|
|||
|
||||
#[doc(inline)]
|
||||
pub use macro_string::{MacroString, MacroStringPart};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use command_collection::CommandCollection;
|
||||
|
|
Loading…
Reference in New Issue