Compare commits
8 Commits
87a113aa9f
...
46499b6abe
Author | SHA1 | Date |
---|---|---|
|
46499b6abe | |
|
76d58c0766 | |
|
588bc3f464 | |
|
8f05fef703 | |
|
897e85c2d7 | |
|
72f98849ea | |
|
2c9fa613ed | |
|
84ee2a5295 |
|
@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- support for commands using macros
|
||||||
|
|
||||||
### 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
|
||||||
|
|
|
@ -22,7 +22,7 @@ fn main() {
|
||||||
bar_function.add_command(call_func);
|
bar_function.add_command(call_func);
|
||||||
// add a complex command to the function "bar"
|
// add a complex command to the function "bar"
|
||||||
bar_function.add_command(Command::Execute(Execute::As(
|
bar_function.add_command(Command::Execute(Execute::As(
|
||||||
"@a".to_string(),
|
"@a".into(),
|
||||||
Box::new(Execute::If(
|
Box::new(Execute::If(
|
||||||
Condition::from("block ~ ~ ~ minecraft:stone")
|
Condition::from("block ~ ~ ~ minecraft:stone")
|
||||||
| !(!Condition::from("block ~ ~1 ~ minecraft:stone")
|
| !(!Condition::from("block ~ ~1 ~ minecraft:stone")
|
||||||
|
|
|
@ -1,31 +1,40 @@
|
||||||
use chksum_md5 as md5;
|
use chksum_md5 as md5;
|
||||||
use std::ops::{BitAnd, BitOr, Not};
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
ops::{BitAnd, BitOr, Not},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::Command,
|
prelude::Command,
|
||||||
util::compile::{CompileOptions, FunctionCompilerState, MutCompilerState},
|
util::{
|
||||||
|
compile::{CompileOptions, CompiledCommand, FunctionCompilerState, MutCompilerState},
|
||||||
|
MacroString,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Execute;
|
use super::Execute;
|
||||||
|
|
||||||
/// Compile an if condition command.
|
/// Compile an if condition command.
|
||||||
/// The first tuple element is a boolean indicating if the prefix should be used for that command.
|
/// The first tuple element is a boolean indicating if the prefix should be used for that command.
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn compile_if_cond(
|
pub fn compile_if_cond(
|
||||||
cond: &Condition,
|
cond: &Condition,
|
||||||
then: &Execute,
|
then: &Execute,
|
||||||
el: Option<&Execute>,
|
el: Option<&Execute>,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
|
prefix_contains_macros: bool,
|
||||||
options: &CompileOptions,
|
options: &CompileOptions,
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<(bool, String)> {
|
) -> Vec<CompiledCommand> {
|
||||||
if options.pack_format < 20 {
|
if options.pack_format < 20 {
|
||||||
compile_pre_20_format(
|
compile_pre_20_format(
|
||||||
cond,
|
cond,
|
||||||
then,
|
then,
|
||||||
el,
|
el,
|
||||||
prefix,
|
prefix,
|
||||||
|
prefix_contains_macros,
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -36,6 +45,7 @@ pub fn compile_if_cond(
|
||||||
then,
|
then,
|
||||||
el,
|
el,
|
||||||
prefix,
|
prefix,
|
||||||
|
prefix_contains_macros,
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -43,16 +53,18 @@ pub fn compile_if_cond(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines, clippy::too_many_arguments)]
|
||||||
fn compile_pre_20_format(
|
fn compile_pre_20_format(
|
||||||
cond: &Condition,
|
cond: &Condition,
|
||||||
then: &Execute,
|
then: &Execute,
|
||||||
el: Option<&Execute>,
|
el: Option<&Execute>,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
|
prefix_contains_macros: bool,
|
||||||
options: &CompileOptions,
|
options: &CompileOptions,
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<(bool, String)> {
|
) -> Vec<CompiledCommand> {
|
||||||
|
let contains_macro = prefix_contains_macros || cond.contains_macro();
|
||||||
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);
|
||||||
|
@ -80,13 +92,28 @@ fn compile_pre_20_format(
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::Group(group_cmd)
|
let group = Command::Group(group_cmd);
|
||||||
|
let allows_prefix = !group.forbid_prefix();
|
||||||
|
group
|
||||||
.compile(options, global_state, function_state)
|
.compile(options, global_state, function_state)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| (true, "run ".to_string() + s))
|
.map(|s| {
|
||||||
|
if allows_prefix {
|
||||||
|
s.clone().apply_prefix("run ")
|
||||||
|
} else {
|
||||||
|
s.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
then.compile_internal(String::new(), false, options, global_state, function_state)
|
then.compile_internal(
|
||||||
|
String::new(),
|
||||||
|
false,
|
||||||
|
contains_macro,
|
||||||
|
options,
|
||||||
|
global_state,
|
||||||
|
function_state,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
// if the conditions have multiple parts joined by a disjunction, commands need to be grouped
|
// if the conditions have multiple parts joined by a disjunction, commands need to be grouped
|
||||||
let each_or_cmd = (str_cond.len() > 1).then(|| {
|
let each_or_cmd = (str_cond.len() > 1).then(|| {
|
||||||
|
@ -98,10 +125,10 @@ fn compile_pre_20_format(
|
||||||
format!("data modify storage shulkerbox:cond {success_uid} set value true"),
|
format!("data modify storage shulkerbox:cond {success_uid} set value true"),
|
||||||
combine_conditions_commands(
|
combine_conditions_commands(
|
||||||
str_cond.clone(),
|
str_cond.clone(),
|
||||||
&[(
|
&[CompiledCommand::new(format!(
|
||||||
true,
|
"run data modify storage shulkerbox:cond {success_uid} set value true"
|
||||||
format!("run data modify storage shulkerbox:cond {success_uid} set value true"),
|
))
|
||||||
)],
|
.or_contains_macros(contains_macro)],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -111,11 +138,10 @@ fn compile_pre_20_format(
|
||||||
tracing::error!("No success_uid found for each_or_cmd, using default");
|
tracing::error!("No success_uid found for each_or_cmd, using default");
|
||||||
"if_success"
|
"if_success"
|
||||||
});
|
});
|
||||||
Condition::Atom(format!("data storage shulkerbox:cond {{{success_uid}:1b}}")).compile(
|
Condition::Atom(MacroString::from(format!(
|
||||||
options,
|
"data storage shulkerbox:cond {{{success_uid}:1b}}"
|
||||||
global_state,
|
)))
|
||||||
function_state,
|
.compile(options, global_state, function_state)
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
str_cond
|
str_cond
|
||||||
};
|
};
|
||||||
|
@ -128,12 +154,14 @@ fn compile_pre_20_format(
|
||||||
tracing::error!("No success_uid found for each_or_cmd, using default");
|
tracing::error!("No success_uid found for each_or_cmd, using default");
|
||||||
"if_success"
|
"if_success"
|
||||||
});
|
});
|
||||||
let else_cond =
|
let else_cond = (!Condition::Atom(MacroString::from(format!(
|
||||||
(!Condition::Atom(format!("data storage shulkerbox:cond {{{success_uid}:1b}}")))
|
"data storage shulkerbox:cond {{{success_uid}:1b}}"
|
||||||
.compile(options, global_state, function_state);
|
))))
|
||||||
|
.compile(options, global_state, function_state);
|
||||||
let el = el.compile_internal(
|
let el = el.compile_internal(
|
||||||
String::new(),
|
String::new(),
|
||||||
else_cond.len() > 1,
|
else_cond.len() > 1,
|
||||||
|
contains_macro,
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -148,10 +176,10 @@ fn compile_pre_20_format(
|
||||||
tracing::error!("No success_uid found for each_or_cmd, using default");
|
tracing::error!("No success_uid found for each_or_cmd, using default");
|
||||||
"if_success"
|
"if_success"
|
||||||
});
|
});
|
||||||
Some((
|
Some(
|
||||||
false,
|
CompiledCommand::new(format!("data remove storage shulkerbox:cond {success_uid}"))
|
||||||
format!("data remove storage shulkerbox:cond {success_uid}"),
|
.with_forbid_prefix(true),
|
||||||
))
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -164,29 +192,27 @@ fn compile_pre_20_format(
|
||||||
.chain(then_commands)
|
.chain(then_commands)
|
||||||
.chain(el_commands)
|
.chain(el_commands)
|
||||||
.chain(reset_success_storage)
|
.chain(reset_success_storage)
|
||||||
.map(|(use_prefix, cmd)| {
|
.map(|cmd| cmd.apply_prefix(prefix))
|
||||||
let cmd = if use_prefix {
|
|
||||||
prefix.to_string() + &cmd
|
|
||||||
} else {
|
|
||||||
cmd
|
|
||||||
};
|
|
||||||
(use_prefix, cmd)
|
|
||||||
})
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
fn compile_since_20_format(
|
fn compile_since_20_format(
|
||||||
cond: &Condition,
|
cond: &Condition,
|
||||||
then: &Execute,
|
then: &Execute,
|
||||||
el: Option<&Execute>,
|
el: Option<&Execute>,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
|
prefix_contains_macros: bool,
|
||||||
options: &CompileOptions,
|
options: &CompileOptions,
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<(bool, String)> {
|
) -> Vec<CompiledCommand> {
|
||||||
|
let contains_macros = prefix_contains_macros || cond.contains_macro();
|
||||||
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_keep_macros(options, global_state, function_state);
|
||||||
|
|
||||||
// if the conditions have multiple parts joined by a disjunction or an else part, commands need to be grouped
|
// if the conditions have multiple parts joined by a disjunction or an else part, commands need to be grouped
|
||||||
if el.is_some() || str_cond.len() > 1 {
|
if el.is_some() || str_cond.len() > 1 {
|
||||||
|
@ -199,45 +225,56 @@ fn compile_since_20_format(
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
);
|
);
|
||||||
Command::Group(group_cmds)
|
let group = Command::Group(group_cmds);
|
||||||
.compile(options, global_state, function_state)
|
let cmds = group.compile(options, global_state, function_state);
|
||||||
.into_iter()
|
if contains_macros {
|
||||||
.map(|s| (true, s))
|
cmds.into_iter()
|
||||||
.collect()
|
.map(|cmd| cmd.or_contains_macros(true))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
cmds
|
||||||
|
}
|
||||||
} else if then_count > 1 {
|
} else if then_count > 1 {
|
||||||
let then_cmd = match then.clone() {
|
let then_cmd = match then.clone() {
|
||||||
Execute::Run(cmd) => vec![*cmd],
|
Execute::Run(cmd) => vec![*cmd],
|
||||||
Execute::Runs(cmds) => cmds,
|
Execute::Runs(cmds) => cmds,
|
||||||
ex => vec![Command::Execute(ex)],
|
ex => vec![Command::Execute(ex)],
|
||||||
};
|
};
|
||||||
let then_cmd_str = Command::Group(then_cmd)
|
let group_cmd = Command::Group(then_cmd);
|
||||||
.compile(options, global_state, function_state)
|
let then_cmd = if group_cmd.forbid_prefix() {
|
||||||
|
group_cmd
|
||||||
|
} else {
|
||||||
|
Command::Concat(
|
||||||
|
Box::new(Command::Raw("run ".to_string())),
|
||||||
|
Box::new(group_cmd),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
combine_conditions_commands_concat(str_cond, &then_cmd)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| (true, format!("run {s}")))
|
.flat_map(|cmd| {
|
||||||
.collect::<Vec<_>>();
|
cmd.compile(options, global_state, function_state)
|
||||||
combine_conditions_commands(str_cond, &then_cmd_str)
|
.into_iter()
|
||||||
.into_iter()
|
.map(move |compiled_cmd| {
|
||||||
.map(|(use_prefix, cmd)| {
|
compiled_cmd
|
||||||
let cmd = if use_prefix {
|
.apply_prefix(prefix)
|
||||||
prefix.to_string() + &cmd
|
.or_contains_macros(contains_macros)
|
||||||
} else {
|
})
|
||||||
cmd
|
|
||||||
};
|
|
||||||
(use_prefix, cmd)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
let str_cmd =
|
str_cond
|
||||||
then.compile_internal(String::new(), false, options, global_state, function_state);
|
|
||||||
combine_conditions_commands(str_cond, &str_cmd)
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(use_prefix, cmd)| {
|
.flat_map(|cond| {
|
||||||
let cmd = if use_prefix {
|
then.compile_internal(
|
||||||
prefix.to_string() + &cmd
|
String::new(),
|
||||||
} else {
|
false,
|
||||||
cmd
|
contains_macros,
|
||||||
};
|
options,
|
||||||
(use_prefix, cmd)
|
global_state,
|
||||||
|
function_state,
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |cmd| cmd.apply_prefix(prefix.to_string() + &cond.compile() + " "))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -245,26 +282,43 @@ fn compile_since_20_format(
|
||||||
|
|
||||||
fn combine_conditions_commands(
|
fn combine_conditions_commands(
|
||||||
conditions: Vec<String>,
|
conditions: Vec<String>,
|
||||||
commands: &[(bool, String)],
|
commands: &[CompiledCommand],
|
||||||
) -> Vec<(bool, String)> {
|
) -> Vec<CompiledCommand> {
|
||||||
conditions
|
conditions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|cond| {
|
.flat_map(|cond| {
|
||||||
commands.iter().map(move |(use_prefix, cmd)| {
|
let prefix = cond + " ";
|
||||||
|
commands.iter().map(move |cmd| {
|
||||||
// combine the condition with the command if it uses a prefix
|
// combine the condition with the command if it uses a prefix
|
||||||
let cmd = if *use_prefix {
|
cmd.clone().apply_prefix(&prefix)
|
||||||
cond.clone() + " " + cmd
|
|
||||||
} else {
|
|
||||||
cmd.clone()
|
|
||||||
};
|
|
||||||
(*use_prefix, cmd)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn combine_conditions_commands_concat(
|
||||||
|
conditions: Vec<MacroString>,
|
||||||
|
command: &Command,
|
||||||
|
) -> Vec<Command> {
|
||||||
|
if command.forbid_prefix() {
|
||||||
|
vec![command.clone()]
|
||||||
|
} else {
|
||||||
|
conditions
|
||||||
|
.into_iter()
|
||||||
|
.map(|cond| {
|
||||||
|
let prefix = if cond.contains_macro() {
|
||||||
|
Command::UsesMacro(cond + " ")
|
||||||
|
} else {
|
||||||
|
Command::Raw(cond.compile() + " ")
|
||||||
|
};
|
||||||
|
Command::Concat(Box::new(prefix), Box::new(command.clone()))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_return_group_case_since_20(
|
fn handle_return_group_case_since_20(
|
||||||
str_cond: Vec<String>,
|
str_cond: Vec<MacroString>,
|
||||||
then: &Execute,
|
then: &Execute,
|
||||||
el: Option<&Execute>,
|
el: Option<&Execute>,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
|
@ -278,15 +332,28 @@ fn handle_return_group_case_since_20(
|
||||||
Execute::Runs(cmds) => cmds,
|
Execute::Runs(cmds) => cmds,
|
||||||
ex => vec![Command::Execute(ex)],
|
ex => vec![Command::Execute(ex)],
|
||||||
};
|
};
|
||||||
let then_cmd_str = Command::Group(then_cmd)
|
let group = Command::Group(then_cmd);
|
||||||
.compile(options, global_state, function_state)
|
let then_cmd_concat = if group.forbid_prefix() {
|
||||||
|
group
|
||||||
|
} else {
|
||||||
|
Command::Concat(
|
||||||
|
Box::new(Command::Raw("run return run ".to_string())),
|
||||||
|
Box::new(group),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let then_cond_concat = combine_conditions_commands_concat(str_cond, &then_cmd_concat);
|
||||||
|
let mut group_cmds = then_cond_concat
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| (true, format!("run return run {s}")))
|
.map(|cmd| {
|
||||||
.collect::<Vec<_>>();
|
if cmd.forbid_prefix() {
|
||||||
let then_cond_str = combine_conditions_commands(str_cond, &then_cmd_str);
|
cmd
|
||||||
let mut group_cmds = then_cond_str
|
} else {
|
||||||
.into_iter()
|
Command::Concat(
|
||||||
.map(|(_, cmd)| Command::Raw(format!("execute {cmd}")))
|
Box::new(Command::Raw("execute ".to_string())),
|
||||||
|
Box::new(cmd),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if let Some(el) = el {
|
if let Some(el) = el {
|
||||||
handle_else_since_20(
|
handle_else_since_20(
|
||||||
|
@ -311,7 +378,7 @@ fn handle_else_since_20(
|
||||||
) {
|
) {
|
||||||
let el_cmd = match el {
|
let el_cmd = match el {
|
||||||
Execute::If(cond, then, el) => handle_return_group_case_since_20(
|
Execute::If(cond, then, el) => handle_return_group_case_since_20(
|
||||||
cond.compile(options, global_state, function_state),
|
cond.compile_keep_macros(options, global_state, function_state),
|
||||||
&then,
|
&then,
|
||||||
el.as_deref(),
|
el.as_deref(),
|
||||||
prefix,
|
prefix,
|
||||||
|
@ -321,7 +388,7 @@ fn handle_else_since_20(
|
||||||
),
|
),
|
||||||
Execute::Run(cmd) => match *cmd {
|
Execute::Run(cmd) => match *cmd {
|
||||||
Command::Execute(Execute::If(cond, then, el)) => handle_return_group_case_since_20(
|
Command::Execute(Execute::If(cond, then, el)) => handle_return_group_case_since_20(
|
||||||
cond.compile(options, global_state, function_state),
|
cond.compile_keep_macros(options, global_state, function_state),
|
||||||
&then,
|
&then,
|
||||||
el.as_deref(),
|
el.as_deref(),
|
||||||
prefix,
|
prefix,
|
||||||
|
@ -343,7 +410,7 @@ fn handle_else_since_20(
|
||||||
#[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)]
|
||||||
pub enum Condition {
|
pub enum Condition {
|
||||||
Atom(String),
|
Atom(MacroString),
|
||||||
Not(Box<Condition>),
|
Not(Box<Condition>),
|
||||||
And(Box<Condition>, Box<Condition>),
|
And(Box<Condition>, Box<Condition>),
|
||||||
Or(Box<Condition>, Box<Condition>),
|
Or(Box<Condition>, Box<Condition>),
|
||||||
|
@ -392,21 +459,21 @@ impl Condition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the condition into a string.
|
/// Convert the condition into a [`MacroString`].
|
||||||
///
|
///
|
||||||
/// Will fail if the condition contains an `Or` variant. Use `compile` instead.
|
/// Will fail if the condition contains an `Or` variant. Use `compile` instead.
|
||||||
fn str_cond(&self) -> Option<String> {
|
fn str_cond(&self) -> Option<MacroString> {
|
||||||
match self {
|
match self {
|
||||||
Self::Atom(s) => Some("if ".to_string() + s),
|
Self::Atom(s) => Some(MacroString::from("if ") + s.clone()),
|
||||||
Self::Not(n) => match *(*n).clone() {
|
Self::Not(n) => match *(*n).clone() {
|
||||||
Self::Atom(s) => Some("unless ".to_string() + &s),
|
Self::Atom(s) => Some(MacroString::from("unless ") + s),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
Self::And(a, b) => {
|
Self::And(a, b) => {
|
||||||
let a = a.str_cond()?;
|
let a = a.str_cond()?;
|
||||||
let b = b.str_cond()?;
|
let b = b.str_cond()?;
|
||||||
|
|
||||||
Some(a + " " + &b)
|
Some(a + MacroString::from(" ") + b)
|
||||||
}
|
}
|
||||||
Self::Or(..) => None,
|
Self::Or(..) => None,
|
||||||
}
|
}
|
||||||
|
@ -414,11 +481,24 @@ impl Condition {
|
||||||
|
|
||||||
/// Compile the condition into a list of strings that can be used in Minecraft.
|
/// Compile the condition into a list of strings that can be used in Minecraft.
|
||||||
pub fn compile(
|
pub fn compile(
|
||||||
|
&self,
|
||||||
|
options: &CompileOptions,
|
||||||
|
global_state: &MutCompilerState,
|
||||||
|
function_state: &FunctionCompilerState,
|
||||||
|
) -> Vec<String> {
|
||||||
|
self.compile_keep_macros(options, global_state, function_state)
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.compile())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the condition into a list of macro strings.
|
||||||
|
pub fn compile_keep_macros(
|
||||||
&self,
|
&self,
|
||||||
_options: &CompileOptions,
|
_options: &CompileOptions,
|
||||||
_global_state: &MutCompilerState,
|
_global_state: &MutCompilerState,
|
||||||
_function_state: &FunctionCompilerState,
|
_function_state: &FunctionCompilerState,
|
||||||
) -> Vec<String> {
|
) -> Vec<MacroString> {
|
||||||
let truth_table = self.to_truth_table();
|
let truth_table = self.to_truth_table();
|
||||||
|
|
||||||
truth_table
|
truth_table
|
||||||
|
@ -427,13 +507,37 @@ impl Condition {
|
||||||
c.str_cond()
|
c.str_cond()
|
||||||
.expect("Truth table should not contain Or variants")
|
.expect("Truth table should not contain Or variants")
|
||||||
})
|
})
|
||||||
.collect()
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the condition contains a macro.
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains_macro(&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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the names of the macros used
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_macros(&self) -> HashSet<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Atom(s) => s.get_macros(),
|
||||||
|
Self::Not(n) => n.get_macros(),
|
||||||
|
Self::And(a, b) | Self::Or(a, b) => {
|
||||||
|
let mut set = a.get_macros();
|
||||||
|
set.extend(b.get_macros());
|
||||||
|
set
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Condition {
|
impl From<&str> for Condition {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
Self::Atom(s.to_string())
|
Self::Atom(s.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,12 +563,6 @@ impl BitOr for Condition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Execute> for Command {
|
|
||||||
fn from(ex: Execute) -> Self {
|
|
||||||
Self::Execute(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -473,8 +571,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_condition() {
|
fn test_condition() {
|
||||||
let c1 = Condition::from("foo");
|
let c1 = Condition::from("foo");
|
||||||
let c2 = Condition::Atom("bar".to_string());
|
let c2 = Condition::Atom("bar".into());
|
||||||
let c3 = Condition::Atom("baz".to_string());
|
let c3 = Condition::Atom("baz".into());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(c1.clone() & c2.clone()).normalize(),
|
(c1.clone() & c2.clone()).normalize(),
|
||||||
|
@ -517,10 +615,10 @@ mod tests {
|
||||||
#[allow(clippy::redundant_clone)]
|
#[allow(clippy::redundant_clone)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_truth_table() {
|
fn test_truth_table() {
|
||||||
let c1 = Condition::Atom("foo".to_string());
|
let c1 = Condition::Atom("foo".into());
|
||||||
let c2 = Condition::Atom("bar".to_string());
|
let c2 = Condition::Atom("bar".into());
|
||||||
let c3 = Condition::Atom("baz".to_string());
|
let c3 = Condition::Atom("baz".into());
|
||||||
let c4 = Condition::Atom("foobar".to_string());
|
let c4 = Condition::Atom("foobar".into());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(c1.clone() & c2.clone()).to_truth_table(),
|
(c1.clone() & c2.clone()).to_truth_table(),
|
||||||
|
@ -562,18 +660,21 @@ mod tests {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(str::to_string)
|
.map(str::to_string)
|
||||||
.collect();
|
.collect();
|
||||||
let commands = &[(true, "1".to_string()), (false, "2".to_string())];
|
let commands = &[
|
||||||
|
CompiledCommand::new("1".to_string()),
|
||||||
|
CompiledCommand::new("2".to_string()).with_forbid_prefix(true),
|
||||||
|
];
|
||||||
|
|
||||||
let combined = combine_conditions_commands(conditions, commands);
|
let combined = combine_conditions_commands(conditions, commands);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
combined,
|
combined,
|
||||||
vec![
|
vec![
|
||||||
(true, "a 1".to_string()),
|
CompiledCommand::new("a 1".to_string()),
|
||||||
(false, "2".to_string()),
|
CompiledCommand::new("2".to_string()).with_forbid_prefix(true),
|
||||||
(true, "b 1".to_string()),
|
CompiledCommand::new("b 1".to_string()),
|
||||||
(false, "2".to_string()),
|
CompiledCommand::new("2".to_string()).with_forbid_prefix(true),
|
||||||
(true, "c 1".to_string()),
|
CompiledCommand::new("c 1".to_string()),
|
||||||
(false, "2".to_string())
|
CompiledCommand::new("2".to_string()).with_forbid_prefix(true)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::ops::RangeInclusive;
|
use std::{collections::HashSet, ops::RangeInclusive, string::ToString};
|
||||||
|
|
||||||
use super::Command;
|
use super::Command;
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
compile::{CompileOptions, FunctionCompilerState, MutCompilerState},
|
compile::{CompileOptions, CompiledCommand, FunctionCompilerState, MutCompilerState},
|
||||||
ExtendableQueue,
|
ExtendableQueue, MacroString,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod conditional;
|
mod conditional;
|
||||||
|
@ -15,18 +15,18 @@ pub use conditional::Condition;
|
||||||
#[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)]
|
||||||
pub enum Execute {
|
pub enum Execute {
|
||||||
Align(String, Box<Execute>),
|
Align(MacroString, Box<Execute>),
|
||||||
Anchored(String, Box<Execute>),
|
Anchored(MacroString, Box<Execute>),
|
||||||
As(String, Box<Execute>),
|
As(MacroString, Box<Execute>),
|
||||||
At(String, Box<Execute>),
|
At(MacroString, Box<Execute>),
|
||||||
AsAt(String, Box<Execute>),
|
AsAt(MacroString, Box<Execute>),
|
||||||
Facing(String, Box<Execute>),
|
Facing(MacroString, Box<Execute>),
|
||||||
In(String, Box<Execute>),
|
In(MacroString, Box<Execute>),
|
||||||
On(String, Box<Execute>),
|
On(MacroString, Box<Execute>),
|
||||||
Positioned(String, Box<Execute>),
|
Positioned(MacroString, Box<Execute>),
|
||||||
Rotated(String, Box<Execute>),
|
Rotated(MacroString, Box<Execute>),
|
||||||
Store(String, Box<Execute>),
|
Store(MacroString, Box<Execute>),
|
||||||
Summon(String, Box<Execute>),
|
Summon(MacroString, Box<Execute>),
|
||||||
If(Condition, Box<Execute>, Option<Box<Execute>>),
|
If(Condition, Box<Execute>, Option<Box<Execute>>),
|
||||||
Run(Box<Command>),
|
Run(Box<Command>),
|
||||||
Runs(Vec<Command>),
|
Runs(Vec<Command>),
|
||||||
|
@ -39,35 +39,38 @@ impl Execute {
|
||||||
options: &CompileOptions,
|
options: &CompileOptions,
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<String> {
|
) -> Vec<CompiledCommand> {
|
||||||
// Directly compile the command if it is a run command, skipping the execute part
|
// Directly compile the command if it is a run command, skipping the execute part
|
||||||
// Otherwise, compile the execute command using internal function
|
// Otherwise, compile the execute command using internal function
|
||||||
if let Self::Run(cmd) = self {
|
match self {
|
||||||
cmd.compile(options, global_state, function_state)
|
Self::Run(cmd) => cmd.compile(options, global_state, function_state),
|
||||||
} else {
|
Self::Runs(cmds) => cmds
|
||||||
self.compile_internal(
|
.iter()
|
||||||
|
.flat_map(|c| c.compile(options, global_state, function_state))
|
||||||
|
.collect(),
|
||||||
|
_ => self.compile_internal(
|
||||||
String::from("execute "),
|
String::from("execute "),
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
)
|
),
|
||||||
.into_iter()
|
|
||||||
.map(|(_, cmd)| cmd)
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile the execute command into strings with the given prefix.
|
/// Compile the execute command into strings with the given prefix.
|
||||||
/// Each first tuple element is a boolean indicating if the prefix should be used for that command.
|
/// Each first tuple element is a boolean indicating if the prefix should be used for that command.
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
fn compile_internal(
|
fn compile_internal(
|
||||||
&self,
|
&self,
|
||||||
prefix: String,
|
prefix: String,
|
||||||
require_grouping: bool,
|
require_grouping: bool,
|
||||||
|
prefix_contains_macros: bool,
|
||||||
options: &CompileOptions,
|
options: &CompileOptions,
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<(bool, String)> {
|
) -> Vec<CompiledCommand> {
|
||||||
match self {
|
match self {
|
||||||
Self::Align(arg, next)
|
Self::Align(arg, next)
|
||||||
| Self::Anchored(arg, next)
|
| Self::Anchored(arg, next)
|
||||||
|
@ -79,15 +82,24 @@ impl Execute {
|
||||||
| Self::Positioned(arg, next)
|
| Self::Positioned(arg, next)
|
||||||
| Self::Rotated(arg, next)
|
| Self::Rotated(arg, next)
|
||||||
| Self::Store(arg, next) => next.compile_internal(
|
| Self::Store(arg, next) => next.compile_internal(
|
||||||
format!("{prefix}{op} {arg} ", op = self.variant_name()),
|
format!(
|
||||||
|
"{prefix}{op} {arg} ",
|
||||||
|
op = self.variant_name(),
|
||||||
|
arg = arg.compile()
|
||||||
|
),
|
||||||
require_grouping,
|
require_grouping,
|
||||||
|
prefix_contains_macros || arg.contains_macro(),
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
),
|
),
|
||||||
Self::AsAt(selector, next) => next.compile_internal(
|
Self::AsAt(selector, next) => next.compile_internal(
|
||||||
format!("{prefix}as {selector} at @s "),
|
format!(
|
||||||
|
"{prefix}as {selector} at @s ",
|
||||||
|
selector = selector.compile()
|
||||||
|
),
|
||||||
require_grouping,
|
require_grouping,
|
||||||
|
prefix_contains_macros || selector.contains_macro(),
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -97,21 +109,28 @@ impl Execute {
|
||||||
then.as_ref(),
|
then.as_ref(),
|
||||||
el.as_deref(),
|
el.as_deref(),
|
||||||
&prefix,
|
&prefix,
|
||||||
|
prefix_contains_macros,
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
),
|
),
|
||||||
Self::Summon(arg, next) => next.compile_internal(
|
Self::Summon(arg, next) => next.compile_internal(
|
||||||
format!("{prefix}{op} {arg} ", op = self.variant_name()),
|
format!(
|
||||||
|
"{prefix}{op} {arg} ",
|
||||||
|
op = self.variant_name(),
|
||||||
|
arg = arg.compile()
|
||||||
|
),
|
||||||
true,
|
true,
|
||||||
|
prefix_contains_macros || arg.contains_macro(),
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
),
|
),
|
||||||
Self::Run(command) => match &**command {
|
Self::Run(command) => match command.as_ref() {
|
||||||
Command::Execute(ex) => ex.compile_internal(
|
Command::Execute(ex) => ex.compile_internal(
|
||||||
prefix,
|
prefix,
|
||||||
require_grouping,
|
require_grouping,
|
||||||
|
prefix_contains_macros,
|
||||||
options,
|
options,
|
||||||
global_state,
|
global_state,
|
||||||
function_state,
|
function_state,
|
||||||
|
@ -119,19 +138,40 @@ impl Execute {
|
||||||
command => command
|
command => command
|
||||||
.compile(options, global_state, function_state)
|
.compile(options, global_state, function_state)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| map_run_cmd(c, &prefix))
|
.map(|c| {
|
||||||
|
map_run_cmd(command.forbid_prefix(), c, &prefix)
|
||||||
|
.or_contains_macros(prefix_contains_macros)
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
Self::Runs(commands) if !require_grouping => commands
|
Self::Runs(commands) if !require_grouping => commands
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|c| c.compile(options, global_state, function_state))
|
.flat_map(|c| match c {
|
||||||
.map(|c| map_run_cmd(c, &prefix))
|
Command::Execute(ex) => ex.compile_internal(
|
||||||
.collect(),
|
prefix.clone(),
|
||||||
Self::Runs(commands) => Command::Group(commands.clone())
|
require_grouping,
|
||||||
.compile(options, global_state, function_state)
|
prefix_contains_macros,
|
||||||
.into_iter()
|
options,
|
||||||
.map(|c| map_run_cmd(c, &prefix))
|
global_state,
|
||||||
|
function_state,
|
||||||
|
),
|
||||||
|
command => command.compile(options, global_state, function_state),
|
||||||
|
})
|
||||||
|
.map(|cmd| {
|
||||||
|
map_run_cmd(false, cmd, &prefix).or_contains_macros(prefix_contains_macros)
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
Self::Runs(commands) => {
|
||||||
|
let group = Command::Group(commands.clone());
|
||||||
|
group
|
||||||
|
.compile(options, global_state, function_state)
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| {
|
||||||
|
map_run_cmd(group.forbid_prefix(), c, &prefix)
|
||||||
|
.or_contains_macros(prefix_contains_macros)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +185,7 @@ impl Execute {
|
||||||
self.compile_internal(
|
self.compile_internal(
|
||||||
String::new(),
|
String::new(),
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
options,
|
options,
|
||||||
&global_state,
|
&global_state,
|
||||||
&function_state,
|
&function_state,
|
||||||
|
@ -200,16 +241,90 @@ impl Execute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether the execute command contains a macro.
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains_macro(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Facing(s, next)
|
||||||
|
| Self::Store(s, next)
|
||||||
|
| Self::Positioned(s, next)
|
||||||
|
| Self::Rotated(s, next)
|
||||||
|
| Self::In(s, next)
|
||||||
|
| Self::As(s, next)
|
||||||
|
| Self::At(s, next)
|
||||||
|
| Self::AsAt(s, next)
|
||||||
|
| Self::Align(s, next)
|
||||||
|
| Self::Anchored(s, next)
|
||||||
|
| Self::Summon(s, next)
|
||||||
|
| Self::On(s, next) => s.contains_macro() || next.contains_macro(),
|
||||||
|
Self::If(cond, then, el) => {
|
||||||
|
cond.contains_macro()
|
||||||
|
|| then.contains_macro()
|
||||||
|
|| el.as_deref().map_or(false, Self::contains_macro)
|
||||||
|
}
|
||||||
|
Self::Run(cmd) => cmd.contains_macro(),
|
||||||
|
Self::Runs(cmds) => cmds.iter().any(super::Command::contains_macro),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the names of the macros used
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_macros(&self) -> HashSet<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Facing(s, _)
|
||||||
|
| Self::Store(s, _)
|
||||||
|
| Self::Positioned(s, _)
|
||||||
|
| Self::Rotated(s, _)
|
||||||
|
| Self::In(s, _)
|
||||||
|
| Self::As(s, _)
|
||||||
|
| Self::At(s, _)
|
||||||
|
| Self::AsAt(s, _)
|
||||||
|
| Self::Align(s, _)
|
||||||
|
| Self::Anchored(s, _)
|
||||||
|
| Self::Summon(s, _)
|
||||||
|
| Self::On(s, _) => s.get_macros(),
|
||||||
|
Self::If(cond, then, el) => {
|
||||||
|
let mut macros = cond.get_macros();
|
||||||
|
macros.extend(then.get_macros());
|
||||||
|
if let Some(el) = el {
|
||||||
|
macros.extend(el.get_macros());
|
||||||
|
}
|
||||||
|
macros
|
||||||
|
}
|
||||||
|
Self::Run(cmd) => cmd.get_macros(),
|
||||||
|
Self::Runs(cmds) => cmds.iter().flat_map(|cmd| cmd.get_macros()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Execute> for Command {
|
||||||
|
fn from(execute: Execute) -> Self {
|
||||||
|
Self::Execute(execute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Command> for Execute {
|
||||||
|
fn from(command: Command) -> Self {
|
||||||
|
Self::Run(Box::new(command))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<V> From<V> for Execute
|
||||||
|
where
|
||||||
|
V: Into<Vec<Command>>,
|
||||||
|
{
|
||||||
|
fn from(value: V) -> Self {
|
||||||
|
Self::Runs(value.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combine command parts, respecting if the second part is a comment
|
/// Combine command parts, respecting if the second part is a comment
|
||||||
/// The first tuple element is a boolean indicating if the prefix should be used
|
/// The first tuple element is a boolean indicating if the prefix should be used
|
||||||
fn map_run_cmd(cmd: String, prefix: &str) -> (bool, String) {
|
fn map_run_cmd(forbid_prefix: bool, cmd: CompiledCommand, prefix: &str) -> CompiledCommand {
|
||||||
if cmd.starts_with('#') || cmd.is_empty() || cmd.chars().all(char::is_whitespace) {
|
let forbid_prefix =
|
||||||
(false, cmd)
|
forbid_prefix || cmd.as_str().is_empty() || cmd.as_str().chars().all(char::is_whitespace);
|
||||||
} else {
|
cmd.or_forbid_prefix(forbid_prefix)
|
||||||
(true, prefix.to_string() + "run " + &cmd)
|
.apply_prefix(prefix.to_string() + "run ")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -219,7 +334,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compile() {
|
fn test_compile() {
|
||||||
let compiled = Execute::As(
|
let compiled = Execute::As(
|
||||||
"@ְa".to_string(),
|
"@a".into(),
|
||||||
Box::new(Execute::If(
|
Box::new(Execute::If(
|
||||||
"block ~ ~-1 ~ minecraft:stone".into(),
|
"block ~ ~-1 ~ minecraft:stone".into(),
|
||||||
Box::new(Execute::Run(Box::new("say hi".into()))),
|
Box::new(Execute::Run(Box::new("say hi".into()))),
|
||||||
|
@ -234,7 +349,9 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compiled,
|
compiled,
|
||||||
vec!["execute as @ְa if block ~ ~-1 ~ minecraft:stone run say hi".to_string()]
|
vec![CompiledCommand::new(
|
||||||
|
"execute as @a if block ~ ~-1 ~ minecraft:stone run say hi"
|
||||||
|
)]
|
||||||
);
|
);
|
||||||
|
|
||||||
let direct = Execute::Run(Box::new("say direct".into())).compile(
|
let direct = Execute::Run(Box::new("say direct".into())).compile(
|
||||||
|
@ -243,6 +360,6 @@ mod tests {
|
||||||
&FunctionCompilerState::default(),
|
&FunctionCompilerState::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(direct, vec!["say direct".to_string()]);
|
assert_eq!(direct, vec![CompiledCommand::new("say direct")]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
//! Represents a command that can be included in a function.
|
//! Represents a command that can be included in a function.
|
||||||
|
|
||||||
mod execute;
|
mod execute;
|
||||||
use std::{collections::HashMap, ops::RangeInclusive, sync::OnceLock};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
ops::RangeInclusive,
|
||||||
|
sync::OnceLock,
|
||||||
|
};
|
||||||
|
|
||||||
pub use execute::{Condition, Execute};
|
pub use execute::{Condition, Execute};
|
||||||
|
|
||||||
|
@ -10,7 +14,10 @@ use chksum_md5 as md5;
|
||||||
use super::Function;
|
use super::Function;
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::Datapack,
|
prelude::Datapack,
|
||||||
util::compile::{CompileOptions, FunctionCompilerState, MutCompilerState},
|
util::{
|
||||||
|
compile::{CompileOptions, CompiledCommand, FunctionCompilerState, MutCompilerState},
|
||||||
|
MacroString,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a command that can be included in a function.
|
/// Represents a command that can be included in a function.
|
||||||
|
@ -19,14 +26,18 @@ use crate::{
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// A command that is already formatted as a string.
|
/// A command that is already formatted as a string.
|
||||||
Raw(String),
|
Raw(String),
|
||||||
|
/// A command that contains macro usages
|
||||||
|
UsesMacro(MacroString),
|
||||||
/// Message to be printed only in debug mode
|
/// Message to be printed only in debug mode
|
||||||
Debug(String),
|
Debug(MacroString),
|
||||||
/// Execute command
|
/// Execute command
|
||||||
Execute(Execute),
|
Execute(Execute),
|
||||||
/// Group of commands to be called instantly after each other
|
/// Group of commands to be called instantly after each other
|
||||||
Group(Vec<Command>),
|
Group(Vec<Command>),
|
||||||
/// Comment to be added to the function
|
/// Comment to be added to the function
|
||||||
Comment(String),
|
Comment(String),
|
||||||
|
/// Command that is a concatenation of two commands
|
||||||
|
Concat(Box<Command>, Box<Command>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
|
@ -42,13 +53,37 @@ impl Command {
|
||||||
options: &CompileOptions,
|
options: &CompileOptions,
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<String> {
|
) -> Vec<CompiledCommand> {
|
||||||
match self {
|
match self {
|
||||||
Self::Raw(command) => vec![command.clone()],
|
Self::Raw(command) => vec![CompiledCommand::new(command.clone())],
|
||||||
|
Self::UsesMacro(command) => {
|
||||||
|
vec![CompiledCommand::new(command.compile()).with_contains_macros(true)]
|
||||||
|
}
|
||||||
Self::Debug(message) => compile_debug(message, options),
|
Self::Debug(message) => compile_debug(message, options),
|
||||||
Self::Execute(ex) => ex.compile(options, global_state, function_state),
|
Self::Execute(ex) => ex.compile(options, global_state, function_state),
|
||||||
Self::Group(commands) => compile_group(commands, options, global_state, function_state),
|
Self::Group(commands) => compile_group(commands, options, global_state, function_state),
|
||||||
Self::Comment(comment) => vec!["#".to_string() + comment],
|
Self::Comment(comment) => {
|
||||||
|
vec![CompiledCommand::new("#".to_string() + comment).with_forbid_prefix(true)]
|
||||||
|
}
|
||||||
|
Self::Concat(a, b) => {
|
||||||
|
let a = a.compile(options, global_state, function_state);
|
||||||
|
let b = b.compile(options, global_state, function_state);
|
||||||
|
a.into_iter()
|
||||||
|
.flat_map(|a| {
|
||||||
|
b.iter().map(move |b| {
|
||||||
|
if a.is_empty() {
|
||||||
|
b.clone()
|
||||||
|
} else if b.is_empty() {
|
||||||
|
a.clone()
|
||||||
|
} else {
|
||||||
|
b.clone()
|
||||||
|
.apply_prefix(a.as_str())
|
||||||
|
.or_forbid_prefix(a.forbids_prefix())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,18 +95,66 @@ impl Command {
|
||||||
Self::Comment(_) => 0,
|
Self::Comment(_) => 0,
|
||||||
Self::Debug(_) => usize::from(options.debug),
|
Self::Debug(_) => usize::from(options.debug),
|
||||||
Self::Raw(cmd) => cmd.split('\n').count(),
|
Self::Raw(cmd) => cmd.split('\n').count(),
|
||||||
|
Self::UsesMacro(cmd) => cmd.line_count(),
|
||||||
Self::Execute(ex) => ex.get_count(options),
|
Self::Execute(ex) => ex.get_count(options),
|
||||||
Self::Group(_) => 1,
|
Self::Group(_) => 1,
|
||||||
|
Self::Concat(a, b) => a.get_count(options) + b.get_count(options) - 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the command is valid with the given pack format.
|
/// Check whether the command is valid with the given pack format.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn validate(&self, pack_formats: &RangeInclusive<u8>) -> bool {
|
pub fn validate(&self, pack_formats: &RangeInclusive<u8>) -> bool {
|
||||||
match self {
|
let command_valid = match self {
|
||||||
Self::Comment(_) | Self::Debug(_) | Self::Group(_) => true,
|
Self::Comment(_) | Self::Debug(_) | Self::Group(_) => 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::Execute(ex) => ex.validate(pack_formats),
|
Self::Execute(ex) => ex.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()
|
||||||
|
} else {
|
||||||
|
command_valid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the command contains a macro.
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains_macro(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Raw(_) | Self::Comment(_) => false,
|
||||||
|
Self::UsesMacro(s) | Self::Debug(s) => s.contains_macro(),
|
||||||
|
Self::Group(commands) => group_contains_macro(commands),
|
||||||
|
Self::Execute(ex) => ex.contains_macro(),
|
||||||
|
Self::Concat(a, b) => a.contains_macro() || b.contains_macro(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the names of the macros used
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_macros(&self) -> HashSet<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Raw(_) | Self::Comment(_) => HashSet::new(),
|
||||||
|
Self::UsesMacro(s) | Self::Debug(s) => s.get_macros(),
|
||||||
|
Self::Group(commands) => group_get_macros(commands),
|
||||||
|
Self::Execute(ex) => ex.get_macros(),
|
||||||
|
Self::Concat(a, b) => {
|
||||||
|
let mut macros = a.get_macros();
|
||||||
|
macros.extend(b.get_macros());
|
||||||
|
macros
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the command should not have a prefix.
|
||||||
|
#[must_use]
|
||||||
|
pub fn forbid_prefix(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Comment(_) => true,
|
||||||
|
Self::Raw(_) | Self::Debug(_) | Self::Execute(_) | Self::UsesMacro(_) => false,
|
||||||
|
Self::Group(commands) => commands.len() == 1 && commands[0].forbid_prefix(),
|
||||||
|
Self::Concat(a, _) => a.forbid_prefix(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,12 +175,12 @@ impl From<&mut Function> for Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_debug(message: &str, option: &CompileOptions) -> Vec<String> {
|
fn compile_debug(message: &MacroString, option: &CompileOptions) -> Vec<CompiledCommand> {
|
||||||
if option.debug {
|
if option.debug {
|
||||||
vec![format!(
|
vec![CompiledCommand::new(format!(
|
||||||
r#"tellraw @a [{{"text":"[","color":"dark_blue"}},{{"text":"DEBUG","color":"dark_green","hoverEvent":{{"action":"show_text","value":[{{"text":"Debug message generated by Shulkerbox"}},{{"text":"\nSet debug message to 'false' to disable"}}]}}}},{{"text":"]","color":"dark_blue"}},{{"text":" {}","color":"black"}}]"#,
|
r#"tellraw @a [{{"text":"[","color":"dark_blue"}},{{"text":"DEBUG","color":"dark_green","hoverEvent":{{"action":"show_text","value":[{{"text":"Debug message generated by Shulkerbox"}},{{"text":"\nSet debug to 'false' to disable"}}]}}}},{{"text":"] ","color":"dark_blue"}},{{"text":"{}","color":"black"}}]"#,
|
||||||
message
|
message.compile()
|
||||||
)]
|
))]
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
@ -109,42 +192,67 @@ fn compile_group(
|
||||||
options: &CompileOptions,
|
options: &CompileOptions,
|
||||||
global_state: &MutCompilerState,
|
global_state: &MutCompilerState,
|
||||||
function_state: &FunctionCompilerState,
|
function_state: &FunctionCompilerState,
|
||||||
) -> Vec<String> {
|
) -> Vec<CompiledCommand> {
|
||||||
let command_count = commands
|
let command_count = commands
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cmd| cmd.get_count(options))
|
.map(|cmd| cmd.get_count(options))
|
||||||
.sum::<usize>();
|
.sum::<usize>();
|
||||||
// only create a function if there are more than one command
|
// only create a function if there are more than one command
|
||||||
if command_count > 1 {
|
match command_count {
|
||||||
let uid = function_state.request_uid();
|
0 => Vec::new(),
|
||||||
|
1 => commands[0].compile(options, global_state, function_state),
|
||||||
|
_ => {
|
||||||
|
let uid = function_state.request_uid();
|
||||||
|
let pass_macros = group_contains_macro(commands);
|
||||||
|
|
||||||
// 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 = {
|
let function_path = {
|
||||||
let function_path = function_state.path();
|
let function_path = function_state.path();
|
||||||
let function_path = function_path.strip_prefix("sb/").unwrap_or(function_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 pre_hash_path = function_path.to_owned() + ":" + &uid.to_string();
|
||||||
let hash = md5::hash(pre_hash_path).to_hex_lowercase();
|
let hash = md5::hash(pre_hash_path).to_hex_lowercase();
|
||||||
|
|
||||||
"sb/".to_string() + function_path + "/" + &hash[..16]
|
"sb/".to_string() + function_path + "/" + &hash[..16]
|
||||||
};
|
};
|
||||||
|
|
||||||
let namespace = function_state.namespace();
|
let namespace = function_state.namespace();
|
||||||
|
|
||||||
// create a new function with the commands
|
// create a new function with the commands
|
||||||
let mut function = Function::new(namespace, &function_path);
|
let mut function = Function::new(namespace, &function_path);
|
||||||
function.get_commands_mut().extend(commands.iter().cloned());
|
function.get_commands_mut().extend(commands.iter().cloned());
|
||||||
function_state.add_function(&function_path, function);
|
function_state.add_function(&function_path, function);
|
||||||
|
|
||||||
vec![format!("function {namespace}:{function_path}")]
|
let mut function_invocation = format!("function {namespace}:{function_path}");
|
||||||
} else {
|
|
||||||
commands
|
if pass_macros {
|
||||||
.iter()
|
// WARNING: this seems to be the only way to pass macros to the function called.
|
||||||
.flat_map(|cmd| cmd.compile(options, global_state, function_state))
|
// Because everything is passed as a string, it looses one "level" of escaping per pass.
|
||||||
.collect::<Vec<_>>()
|
let macros_block = group_get_macros(commands)
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| format!(r#"{m}:"$({m})""#))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
|
function_invocation.push_str(&format!(" {{{macros_block}}}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![CompiledCommand::new(function_invocation).with_contains_macros(pass_macros)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn group_contains_macro(commands: &[Command]) -> bool {
|
||||||
|
commands.iter().any(Command::contains_macro)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_get_macros(commands: &[Command]) -> HashSet<&str> {
|
||||||
|
let mut macros = HashSet::new();
|
||||||
|
for cmd in commands {
|
||||||
|
macros.extend(cmd.get_macros());
|
||||||
|
}
|
||||||
|
macros
|
||||||
|
}
|
||||||
|
|
||||||
#[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: OnceLock<HashMap<&str, RangeInclusive<u8>>> = OnceLock::new();
|
static CMD_FORMATS: OnceLock<HashMap<&str, RangeInclusive<u8>>> = OnceLock::new();
|
||||||
|
@ -287,12 +395,12 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
command_a.compile(options, global_state, function_state),
|
command_a.compile(options, global_state, function_state),
|
||||||
vec!["say Hello, world!".to_string()]
|
vec![CompiledCommand::new("say Hello, world!")]
|
||||||
);
|
);
|
||||||
assert_eq!(command_a.get_count(options), 1);
|
assert_eq!(command_a.get_count(options), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
command_b.compile(options, global_state, function_state),
|
command_b.compile(options, global_state, function_state),
|
||||||
vec!["say foo bar".to_string()]
|
vec![CompiledCommand::new("say foo bar")]
|
||||||
);
|
);
|
||||||
assert_eq!(command_b.get_count(options), 1);
|
assert_eq!(command_b.get_count(options), 1);
|
||||||
}
|
}
|
||||||
|
@ -307,7 +415,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
comment.compile(options, global_state, function_state),
|
comment.compile(options, global_state, function_state),
|
||||||
vec!["#this is a comment".to_string()]
|
vec![CompiledCommand::new("#this is a comment").with_forbid_prefix(true)]
|
||||||
);
|
);
|
||||||
assert_eq!(comment.get_count(options), 0);
|
assert_eq!(comment.get_count(options), 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,26 @@ impl Function {
|
||||||
let content = self
|
let content = self
|
||||||
.commands
|
.commands
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|c| c.compile(options, global_state, function_state))
|
.flat_map(|c| {
|
||||||
.collect::<Vec<String>>()
|
let cmds = c.compile(options, global_state, function_state);
|
||||||
|
|
||||||
|
if c.contains_macro() {
|
||||||
|
cmds.into_iter()
|
||||||
|
.map(|c| {
|
||||||
|
if c.contains_macros() {
|
||||||
|
let content = format!("${c}");
|
||||||
|
c.with_command(content)
|
||||||
|
} else {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
cmds.into_iter().map(|c| c.to_string()).collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
VFile::Text(content)
|
VFile::Text(content)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Compile options for the compiler.
|
//! Compile options for the compiler.
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::{fmt::Display, ops::Deref, sync::Mutex};
|
||||||
|
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
|
||||||
|
@ -86,3 +86,127 @@ impl FunctionCompilerState {
|
||||||
uid
|
uid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compiled command, ready to be written to a function.
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub struct CompiledCommand {
|
||||||
|
/// The command string.
|
||||||
|
command: String,
|
||||||
|
/// Whether the command is not allowed to be prefixed.
|
||||||
|
forbid_prefix: bool,
|
||||||
|
/// Whether the command contains a macro.
|
||||||
|
contains_macros: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompiledCommand {
|
||||||
|
/// Create a new compiled command.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new<S>(command: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
command: command.into(),
|
||||||
|
forbid_prefix: false,
|
||||||
|
contains_macros: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the command string.
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.command
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the command string.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_command(mut self, command: String) -> Self {
|
||||||
|
self.command = command;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the command is forbidden to be prefixed.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_forbid_prefix(mut self, forbid_prefix: bool) -> Self {
|
||||||
|
self.forbid_prefix = forbid_prefix;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the command contains a macro.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_contains_macros(mut self, contains_macros: bool) -> Self {
|
||||||
|
self.contains_macros = contains_macros;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get whether the command is forbidden to be prefixed.
|
||||||
|
#[must_use]
|
||||||
|
pub fn forbids_prefix(&self) -> bool {
|
||||||
|
self.forbid_prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get whether the command contains a macro.
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains_macros(&self) -> bool {
|
||||||
|
self.contains_macros
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a prefix to the command (if allowed).
|
||||||
|
#[must_use]
|
||||||
|
pub fn apply_prefix<S>(mut self, prefix: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
if !self.forbid_prefix {
|
||||||
|
self.command = prefix.into() + &self.command;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combine current forbid prefix status with the input.
|
||||||
|
#[must_use]
|
||||||
|
pub fn or_forbid_prefix(mut self, forbid_prefix: bool) -> Self {
|
||||||
|
self.forbid_prefix = self.forbid_prefix || forbid_prefix;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combine current contains macro status with the input.
|
||||||
|
#[must_use]
|
||||||
|
pub fn or_contains_macros(mut self, contains_macros: bool) -> Self {
|
||||||
|
self.contains_macros = self.contains_macros || contains_macros;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for CompiledCommand {
|
||||||
|
type Target = String;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CompiledCommand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CompiledCommand> for String {
|
||||||
|
fn from(compiled_command: CompiledCommand) -> Self {
|
||||||
|
compiled_command.command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for CompiledCommand {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for CompiledCommand {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self::new(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
|
||||||
|
use std::{borrow::Cow, collections::HashSet, ops::Add};
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MacroString {
|
||||||
|
String(String),
|
||||||
|
MacroString(Vec<MacroStringPart>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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::<Vec<_>>()
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the amount of lines the string has
|
||||||
|
#[must_use]
|
||||||
|
pub fn line_count(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::String(s) => s.split('\n').count(),
|
||||||
|
Self::MacroString(parts) => {
|
||||||
|
parts
|
||||||
|
.iter()
|
||||||
|
.map(|p| match p {
|
||||||
|
MacroStringPart::String(s) => s.split('\n').count() - 1,
|
||||||
|
MacroStringPart::MacroUsage(_) => 0,
|
||||||
|
})
|
||||||
|
.sum::<usize>()
|
||||||
|
+ 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the names of the macros used in the [`MacroString`]
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_macros(&self) -> HashSet<&str> {
|
||||||
|
match self {
|
||||||
|
Self::String(_) => HashSet::new(),
|
||||||
|
Self::MacroString(parts) => parts
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| match p {
|
||||||
|
MacroStringPart::String(_) => None,
|
||||||
|
MacroStringPart::MacroUsage(m) => Some(m.as_str()),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> 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<String> 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for MacroString {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Self::String(mut s1), Self::String(s2)) => {
|
||||||
|
s1.push_str(&s2);
|
||||||
|
Self::String(s1)
|
||||||
|
}
|
||||||
|
(Self::String(s1), Self::MacroString(s2)) => Self::MacroString(
|
||||||
|
std::iter::once(MacroStringPart::String(s1))
|
||||||
|
.chain(s2)
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
(Self::MacroString(mut s1), Self::String(s2)) => {
|
||||||
|
s1.push(MacroStringPart::String(s2));
|
||||||
|
Self::MacroString(s1)
|
||||||
|
}
|
||||||
|
(Self::MacroString(mut s1), Self::MacroString(s2)) => {
|
||||||
|
s1.extend(s2);
|
||||||
|
Self::MacroString(s1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&str> for MacroString {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: &str) -> Self::Output {
|
||||||
|
match self {
|
||||||
|
Self::String(mut s1) => {
|
||||||
|
s1.push_str(rhs);
|
||||||
|
Self::String(s1)
|
||||||
|
}
|
||||||
|
Self::MacroString(mut s1) => {
|
||||||
|
s1.push(MacroStringPart::String(rhs.to_string()));
|
||||||
|
Self::MacroString(s1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
pub mod compile;
|
pub mod compile;
|
||||||
mod extendable_queue;
|
mod extendable_queue;
|
||||||
|
mod macro_string;
|
||||||
pub(crate) mod pack_format;
|
pub(crate) mod pack_format;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use extendable_queue::ExtendableQueue;
|
pub use extendable_queue::ExtendableQueue;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use macro_string::{MacroString, MacroStringPart};
|
||||||
|
|
Loading…
Reference in New Issue