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 commands using macros
|
||||||
- support for registering scoreboards (automatic creation and deletion)
|
- support for registering scoreboards (automatic creation and deletion)
|
||||||
- "return" command with special handling in groups and conditionals
|
- "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
|
### Changed
|
||||||
- use "return" command for conditionals instead of data storage when using supported pack format
|
- 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
|
### Removed
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ fn compile_using_data_storage(
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<CompiledCommand> {
|
) -> 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 then_count = then.get_count(options);
|
||||||
|
|
||||||
let str_cond = cond.clone().compile(options, global_state, function_state);
|
let str_cond = cond.clone().compile(options, global_state, function_state);
|
||||||
|
@ -208,7 +208,7 @@ fn compile_since_20_format(
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<CompiledCommand> {
|
) -> 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 then_count = then.get_count(options);
|
||||||
|
|
||||||
let str_cond = cond
|
let str_cond = cond
|
||||||
|
@ -321,7 +321,7 @@ fn combine_conditions_commands_concat(
|
||||||
conditions
|
conditions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|cond| {
|
.map(|cond| {
|
||||||
let prefix = if cond.contains_macro() {
|
let prefix = if cond.contains_macros() {
|
||||||
Command::UsesMacro(cond + " ")
|
Command::UsesMacro(cond + " ")
|
||||||
} else {
|
} else {
|
||||||
Command::Raw(cond.compile() + " ")
|
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`].
|
/// Convert the condition into a [`MacroString`].
|
||||||
///
|
///
|
||||||
/// Will fail if the condition contains an `Or` or double nested `Not` variant. Use `compile` instead.
|
/// 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.
|
/// Check whether the condition contains a macro.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains_macro(&self) -> bool {
|
pub fn contains_macros(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Atom(s) => s.contains_macro(),
|
Self::Atom(s) => s.contains_macros(),
|
||||||
Self::Not(n) => n.contains_macro(),
|
Self::Not(n) => n.contains_macros(),
|
||||||
Self::And(a, b) | Self::Or(a, b) => a.contains_macro() || b.contains_macro(),
|
Self::And(a, b) | Self::Or(a, b) => a.contains_macros() || b.contains_macros(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ impl Execute {
|
||||||
arg = arg.compile()
|
arg = arg.compile()
|
||||||
),
|
),
|
||||||
require_grouping,
|
require_grouping,
|
||||||
prefix_contains_macros || arg.contains_macro(),
|
prefix_contains_macros || arg.contains_macros(),
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -102,7 +102,7 @@ impl Execute {
|
||||||
selector = selector.compile()
|
selector = selector.compile()
|
||||||
),
|
),
|
||||||
require_grouping,
|
require_grouping,
|
||||||
prefix_contains_macros || selector.contains_macro(),
|
prefix_contains_macros || selector.contains_macros(),
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -124,7 +124,7 @@ impl Execute {
|
||||||
arg = arg.compile()
|
arg = arg.compile()
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
prefix_contains_macros || arg.contains_macro(),
|
prefix_contains_macros || arg.contains_macros(),
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -247,7 +247,7 @@ impl Execute {
|
||||||
|
|
||||||
/// Check whether the execute command contains a macro.
|
/// Check whether the execute command contains a macro.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains_macro(&self) -> bool {
|
pub fn contains_macros(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Facing(s, next)
|
Self::Facing(s, next)
|
||||||
| Self::Store(s, next)
|
| Self::Store(s, next)
|
||||||
|
@ -260,14 +260,14 @@ impl Execute {
|
||||||
| Self::Align(s, next)
|
| Self::Align(s, next)
|
||||||
| Self::Anchored(s, next)
|
| Self::Anchored(s, next)
|
||||||
| Self::Summon(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) => {
|
Self::If(cond, then, el) => {
|
||||||
cond.contains_macro()
|
cond.contains_macros()
|
||||||
|| then.contains_macro()
|
|| then.contains_macros()
|
||||||
|| el.as_deref().is_some_and(Self::contains_macro)
|
|| el.as_deref().is_some_and(Self::contains_macros)
|
||||||
}
|
}
|
||||||
Self::Run(cmd) => cmd.contains_macro(),
|
Self::Run(cmd) => cmd.contains_macros(),
|
||||||
Self::Runs(cmds) => cmds.iter().any(super::Command::contains_macro),
|
Self::Runs(cmds) => cmds.iter().any(super::Command::contains_macros),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::{
|
||||||
prelude::Datapack,
|
prelude::Datapack,
|
||||||
util::{
|
util::{
|
||||||
compile::{CompileOptions, CompiledCommand, FunctionCompilerState, MutCompilerState},
|
compile::{CompileOptions, CompiledCommand, FunctionCompilerState, MutCompilerState},
|
||||||
MacroString,
|
CommandCollection, MacroString, MacroStringPart,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@ pub enum Command {
|
||||||
Comment(String),
|
Comment(String),
|
||||||
/// Return value
|
/// Return value
|
||||||
Return(ReturnCommand),
|
Return(ReturnCommand),
|
||||||
|
/// While loop
|
||||||
|
While(While),
|
||||||
|
|
||||||
/// Command that is a concatenation of two commands
|
/// Command that is a concatenation of two commands
|
||||||
Concat(Box<Command>, Box<Command>),
|
Concat(Box<Command>, Box<Command>),
|
||||||
|
@ -70,6 +72,8 @@ impl Command {
|
||||||
vec![CompiledCommand::new("#".to_string() + comment).with_forbid_prefix(true)]
|
vec![CompiledCommand::new("#".to_string() + comment).with_forbid_prefix(true)]
|
||||||
}
|
}
|
||||||
Self::Return(return_cmd) => return_cmd.compile(options, global_state, function_state),
|
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) => {
|
Self::Concat(a, b) => {
|
||||||
let a = a.compile(options, global_state, function_state);
|
let a = a.compile(options, global_state, function_state);
|
||||||
let b = b.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::Execute(ex) => ex.get_count(options),
|
||||||
Self::Group(group) => group.get_count(options),
|
Self::Group(group) => group.get_count(options),
|
||||||
Self::Return(_) => 1,
|
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,
|
Self::Concat(a, b) => a.get_count(options) + b.get_count(options) - 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,7 +118,7 @@ impl Command {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn validate(&self, pack_formats: &RangeInclusive<u8>) -> bool {
|
pub fn validate(&self, pack_formats: &RangeInclusive<u8>) -> bool {
|
||||||
let command_valid = match self {
|
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::Raw(cmd) => validate_raw_cmd(cmd, pack_formats),
|
||||||
Self::UsesMacro(cmd) => validate_raw_cmd(&cmd.compile(), pack_formats),
|
Self::UsesMacro(cmd) => validate_raw_cmd(&cmd.compile(), pack_formats),
|
||||||
Self::Execute(ex) => ex.validate(pack_formats),
|
Self::Execute(ex) => ex.validate(pack_formats),
|
||||||
|
@ -122,10 +128,11 @@ impl Command {
|
||||||
pack_formats.start() >= &16 && cmd.validate(pack_formats)
|
pack_formats.start() >= &16 && cmd.validate(pack_formats)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Concat(a, b) => a.validate(pack_formats) && b.validate(pack_formats),
|
Self::Concat(a, b) => a.validate(pack_formats) && b.validate(pack_formats),
|
||||||
};
|
};
|
||||||
if pack_formats.start() < &16 {
|
if pack_formats.start() < &16 {
|
||||||
command_valid && !self.contains_macro()
|
command_valid && !self.contains_macros()
|
||||||
} else {
|
} else {
|
||||||
command_valid
|
command_valid
|
||||||
}
|
}
|
||||||
|
@ -133,17 +140,19 @@ impl Command {
|
||||||
|
|
||||||
/// Check whether the command contains a macro.
|
/// Check whether the command contains a macro.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains_macro(&self) -> bool {
|
pub fn contains_macros(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Raw(_) | Self::Comment(_) => false,
|
Self::Raw(_) | Self::Comment(_) => false,
|
||||||
Self::UsesMacro(s) | Self::Debug(s) => s.contains_macro(),
|
Self::UsesMacro(s) | Self::Debug(s) => s.contains_macros(),
|
||||||
Self::Group(group) => group.contains_macro(),
|
Self::Group(group) => group.contains_macros(),
|
||||||
Self::Execute(ex) => ex.contains_macro(),
|
Self::Execute(ex) => ex.contains_macros(),
|
||||||
Self::Return(ret) => match ret {
|
Self::Return(ret) => match ret {
|
||||||
ReturnCommand::Value(value) => value.contains_macro(),
|
ReturnCommand::Value(value) => value.contains_macros(),
|
||||||
ReturnCommand::Command(cmd) => cmd.contains_macro(),
|
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::Value(value) => value.get_macros(),
|
||||||
ReturnCommand::Command(cmd) => cmd.get_macros(),
|
ReturnCommand::Command(cmd) => cmd.get_macros(),
|
||||||
},
|
},
|
||||||
|
Self::While(while_cmd) => while_cmd.get_macros(),
|
||||||
|
|
||||||
Self::Concat(a, b) => {
|
Self::Concat(a, b) => {
|
||||||
let mut macros = a.get_macros();
|
let mut macros = a.get_macros();
|
||||||
macros.extend(b.get_macros());
|
macros.extend(b.get_macros());
|
||||||
|
@ -172,12 +183,17 @@ impl Command {
|
||||||
pub fn forbid_prefix(&self) -> bool {
|
pub fn forbid_prefix(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Comment(_) => true,
|
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::Group(group) => group.forbid_prefix(),
|
||||||
Self::Return(ret) => match ret {
|
Self::Return(ret) => match ret {
|
||||||
ReturnCommand::Value(_) => false,
|
ReturnCommand::Value(_) => false,
|
||||||
ReturnCommand::Command(cmd) => cmd.forbid_prefix(),
|
ReturnCommand::Command(cmd) => cmd.forbid_prefix(),
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Concat(a, _) => a.forbid_prefix(),
|
Self::Concat(a, _) => a.forbid_prefix(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,11 +204,13 @@ impl Command {
|
||||||
match self {
|
match self {
|
||||||
Self::Comment(_) | Self::Debug(_) => false,
|
Self::Comment(_) | Self::Debug(_) => false,
|
||||||
Self::Return(_) => true,
|
Self::Return(_) => true,
|
||||||
Self::Concat(a, b) => a.contains_return() || b.contains_return(),
|
|
||||||
Self::Execute(exec) => exec.contains_return(),
|
Self::Execute(exec) => exec.contains_return(),
|
||||||
Self::Raw(cmd) => cmd.starts_with("return "),
|
Self::Raw(cmd) => cmd.starts_with("return "),
|
||||||
Self::UsesMacro(m) => m.compile().starts_with("return "),
|
Self::UsesMacro(m) => m.compile().starts_with("return "),
|
||||||
Self::Group(g) => g.contains_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.
|
/// Represents a group of commands to be executed in sequence.
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -279,25 +315,40 @@ impl Group {
|
||||||
self.commands[0].compile(options, global_state, function_state)
|
self.commands[0].compile(options, global_state, function_state)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let pass_macros = self.contains_macro();
|
let commands = &self.commands;
|
||||||
let contains_return = self.contains_return();
|
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
|
// 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();
|
let namespace = function_state.namespace();
|
||||||
|
|
||||||
// create a new function with the commands
|
let contained_macros = {
|
||||||
let mut function = Function::new(namespace, &function_path);
|
let mut macros = HashSet::new();
|
||||||
function
|
for cmd in commands {
|
||||||
.get_commands_mut()
|
macros.extend(cmd.get_macros());
|
||||||
.extend(self.commands.iter().cloned());
|
}
|
||||||
function_state.add_function(&function_path, function);
|
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 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 return_data_path = md5::hash(&full_path).to_hex_lowercase();
|
||||||
|
|
||||||
let pre_cmds = Command::Raw(format!(
|
let pre_cmds = Command::Raw(format!(
|
||||||
|
@ -312,23 +363,23 @@ impl Group {
|
||||||
);
|
);
|
||||||
|
|
||||||
let post_cmd_store = global_state
|
let post_cmd_store = global_state
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.functions_with_special_return
|
.functions_with_special_return
|
||||||
.get(&format!(
|
.get(&format!(
|
||||||
"{}:{}",
|
"{}:{}",
|
||||||
function_state.namespace(),
|
function_state.namespace(),
|
||||||
function_state.path()
|
function_state.path()
|
||||||
))
|
))
|
||||||
.cloned().map(|parent_return_data_path| {
|
.cloned().map(|parent_return_data_path| {
|
||||||
Command::Execute(Execute::If(
|
Command::Execute(Execute::If(
|
||||||
post_condition.clone(),
|
post_condition.clone(),
|
||||||
Box::new(Execute::Run(Box::new(Command::Raw(format!(
|
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}"
|
"data modify storage shulkerbox:return {parent_return_data_path} set from storage shulkerbox:return {return_data_path}"
|
||||||
))))),
|
))))),
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
let post_cmd_return = Command::Execute(Execute::If(
|
let post_cmd_return = Command::Execute(Execute::If(
|
||||||
post_condition,
|
post_condition,
|
||||||
|
@ -356,72 +407,26 @@ impl Group {
|
||||||
None
|
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((mut pre_cmds, post_cmds)) = additional_return_cmds {
|
||||||
if let Some(prepare_datastorage_cmd) = prepare_data_storage {
|
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(
|
pre_cmds.extend(function_invocation.compile(
|
||||||
CompiledCommand::new(function_invocation)
|
options,
|
||||||
.with_contains_macros(pass_macros && self.data_storage_name.is_none()),
|
global_state,
|
||||||
);
|
function_state,
|
||||||
|
));
|
||||||
pre_cmds.extend(post_cmds);
|
pre_cmds.extend(post_cmds);
|
||||||
pre_cmds
|
pre_cmds
|
||||||
} else {
|
} else {
|
||||||
prepare_data_storage
|
prepare_data_storage
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(std::iter::once(
|
.flat_map(|prep| prep.compile(options, global_state, function_state))
|
||||||
CompiledCommand::new(function_invocation).with_contains_macros(
|
.chain(function_invocation.compile(options, global_state, function_state))
|
||||||
pass_macros && self.data_storage_name.is_none(),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -460,27 +465,14 @@ impl Group {
|
||||||
|
|
||||||
/// Check whether the group contains a macro.
|
/// Check whether the group contains a macro.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains_macro(&self) -> bool {
|
pub fn contains_macros(&self) -> bool {
|
||||||
self.commands.iter().any(Command::contains_macro)
|
self.commands.contains_macros()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the group contains a return command.
|
/// Check whether the group contains a return command.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains_return(&self) -> bool {
|
pub fn contains_return(&self) -> bool {
|
||||||
self.commands.iter().any(Command::contains_return)
|
self.commands.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]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the names of the macros used
|
/// Returns the names of the macros used
|
||||||
|
@ -512,7 +504,7 @@ impl Group {
|
||||||
0 if !self.always_create_function => 0,
|
0 if !self.always_create_function => 0,
|
||||||
1 if !self.always_create_function => 1,
|
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 contains_return = self.contains_return();
|
||||||
|
|
||||||
let additional_return_cmds = if 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.
|
/// Represents a command that returns a value.
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[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)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn validate_raw_cmd(cmd: &str, pack_formats: &RangeInclusive<u8>) -> bool {
|
fn validate_raw_cmd(cmd: &str, pack_formats: &RangeInclusive<u8>) -> bool {
|
||||||
static CMD_FORMATS: LazyLock<HashMap<&str, RangeInclusive<u8>>> = LazyLock::new(|| {
|
static CMD_FORMATS: LazyLock<HashMap<&str, RangeInclusive<u8>>> = LazyLock::new(|| {
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl Function {
|
||||||
.flat_map(|c| {
|
.flat_map(|c| {
|
||||||
let cmds = c.compile(options, global_state, function_state);
|
let cmds = c.compile(options, global_state, function_state);
|
||||||
|
|
||||||
if c.contains_macro() {
|
if c.contains_macros() {
|
||||||
cmds.into_iter()
|
cmds.into_iter()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
if c.contains_macros() {
|
if c.contains_macros() {
|
||||||
|
|
|
@ -4,7 +4,7 @@ mod command;
|
||||||
mod function;
|
mod function;
|
||||||
mod namespace;
|
mod namespace;
|
||||||
pub mod tag;
|
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 function::Function;
|
||||||
pub use namespace::Namespace;
|
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 {
|
impl MacroString {
|
||||||
/// Returns whether the [`MacroString`] contains any macro usages
|
/// Returns whether the [`MacroString`] contains any macro usages
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contains_macro(&self) -> bool {
|
pub fn contains_macros(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::String(_) => false,
|
Self::String(_) => false,
|
||||||
Self::MacroString(parts) => !parts
|
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
|
/// Returns the amount of lines the string has
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn line_count(&self) -> usize {
|
pub fn line_count(&self) -> usize {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Utility functions for the Shulkerbox project.
|
//! Utility functions for the Shulkerbox project.
|
||||||
|
|
||||||
|
mod command_collection;
|
||||||
pub mod compile;
|
pub mod compile;
|
||||||
mod extendable_queue;
|
mod extendable_queue;
|
||||||
mod macro_string;
|
mod macro_string;
|
||||||
|
@ -10,3 +11,6 @@ pub use extendable_queue::ExtendableQueue;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use macro_string::{MacroString, MacroStringPart};
|
pub use macro_string::{MacroString, MacroStringPart};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use command_collection::CommandCollection;
|
||||||
|
|
Loading…
Reference in New Issue