Basic implementation of execute

This commit is contained in:
Moritz Hölting 2024-03-28 00:58:46 +01:00
parent 6572243ce4
commit c494031163
7 changed files with 342 additions and 48 deletions

View File

@ -10,6 +10,7 @@ default = ["zip"]
zip = ["dep:zip"] zip = ["dep:zip"]
[dependencies] [dependencies]
getset = "0.1.2"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114" serde_json = "1.0.114"
zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"], optional = true } zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"], optional = true }

View File

@ -15,13 +15,10 @@ let mut dp = Datapack::new("shulkerpack", 20) // Create a new datapack with the
.with_supported_formats(16..=20) // Add the supported formats of the datapack .with_supported_formats(16..=20) // Add the supported formats of the datapack
.with_template_folder(Path::new("./template")) // Add a template folder to the datapack. This will include all files in the template folder in the root of the datapack and can be used for including the "pack.png" file .with_template_folder(Path::new("./template")) // Add a template folder to the datapack. This will include all files in the template folder in the root of the datapack and can be used for including the "pack.png" file
.unwrap(); .unwrap();
let mut namespace = Namespace::new("shulker"); // Create a new namespace with the name "shulker" let namespace = datapack.namespace_mut("shulker"); // Create a new namespace with the name "shulker"
let mut hello_function = Function::new(); // Create a new function let hello_function = namespace.function_mut("hello"); // Create a new function
hello_function.add_command("say Hello, world!".into()); // Add a command to the function hello_function.add_command("say Hello, world!"); // Add a command to the function
namespace.add_function("hello", hello_function); // Add the function to the namespace
dp.add_namespace(namespace); // Add the namespace to the datapack
let v_folder = dp.compile(&CompileOptions::default()); // Compile the datapack with default options let v_folder = dp.compile(&CompileOptions::default()); // Compile the datapack with default options

View File

@ -1,9 +1,12 @@
use std::ops::{BitAnd, BitOr, Not}; use std::ops::{BitAnd, BitOr, Deref, Not};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::util::compile::{CompileOptions, MutCompilerState, MutFunctionCompilerState};
use super::Command; use super::Command;
#[allow(missing_docs)]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Execute { pub enum Execute {
Align(String, Box<Execute>), Align(String, Box<Execute>),
@ -18,10 +21,228 @@ pub enum Execute {
Rotated(String, Box<Execute>), Rotated(String, Box<Execute>),
Store(String, Box<Execute>), Store(String, Box<Execute>),
Summon(String, Box<Execute>), Summon(String, Box<Execute>),
If(Condition, Box<Execute>), If(Condition, Box<Execute>, Option<Box<Execute>>),
Run(String, Box<Command>), Run(Box<Command>),
Runs(Vec<Command>),
} }
impl Execute {
/// Compile the execute command into a list of strings.
pub fn compile(
&self,
options: &CompileOptions,
global_state: &MutCompilerState,
function_state: &MutFunctionCompilerState,
) -> Vec<String> {
self.compile_internal(
String::from("execute "),
false,
options,
global_state,
function_state,
)
}
fn compile_internal(
&self,
prefix: String,
require_grouping: bool,
options: &CompileOptions,
global_state: &MutCompilerState,
function_state: &MutFunctionCompilerState,
) -> Vec<String> {
match self {
Self::Align(align, next) => format_execute(
prefix,
&format!("align {align} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::Anchored(anchor, next) => format_execute(
prefix,
&format!("anchored {anchor} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::As(selector, next) => format_execute(
prefix,
&format!("as {selector} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::At(selector, next) => format_execute(
prefix,
&format!("at {selector} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::AsAt(selector, next) => format_execute(
prefix,
&format!("as {selector} at @s "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::Facing(facing, next) => format_execute(
prefix,
&format!("facing {facing} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::In(dim, next) => format_execute(
prefix,
&format!("in {dim} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::On(dim, next) => format_execute(
prefix,
&format!("on {dim} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::Positioned(pos, next) => format_execute(
prefix,
&format!("positioned {pos} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::Rotated(rot, next) => format_execute(
prefix,
&format!("rotated {rot} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::Store(store, next) => format_execute(
prefix,
&format!("store {store} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::Summon(entity, next) => format_execute(
prefix,
&format!("summon {entity} "),
next,
require_grouping,
options,
global_state,
function_state,
),
Self::If(cond, then, el) => {
// TODO: group commands if needed and only run else commands if the first commands have not executed
let str_cond = cond.clone().compile(options, global_state, function_state);
let require_grouping = str_cond.len() > 1;
let then = then.compile_internal(
String::new(),
require_grouping,
options,
global_state,
function_state,
);
let then_commands = combine_conditions_commands(str_cond, then);
let el = el
.as_deref()
.map(|el| {
let else_cond =
(!cond.clone()).compile(options, global_state, function_state);
let el = el.compile_internal(
String::new(),
else_cond.len() > 1,
options,
global_state,
function_state,
);
combine_conditions_commands(else_cond, el)
})
.unwrap_or_default();
then_commands
.into_iter()
.chain(el)
.map(|cmd| prefix.clone() + &cmd + " ")
.collect()
}
Self::Run(command) if !require_grouping => command
.compile(options, global_state, function_state)
.into_iter()
.map(|c| prefix.clone() + "run " + &c)
.collect(),
Self::Run(command) => Command::Group(vec![command.deref().clone()])
.compile(options, global_state, function_state)
.into_iter()
.map(|c| prefix.clone() + "run " + &c)
.collect(),
Self::Runs(commands) if !require_grouping => commands
.iter()
.flat_map(|c| c.compile(options, global_state, function_state))
.map(|c| prefix.clone() + "run " + &c)
.collect(),
Self::Runs(commands) => Command::Group(commands.clone())
.compile(options, global_state, function_state)
.into_iter()
.map(|c| prefix.clone() + "run " + &c)
.collect(),
}
}
}
fn format_execute(
prefix: String,
new: &str,
next: &Execute,
require_grouping: bool,
options: &CompileOptions,
global_state: &MutCompilerState,
function_state: &MutFunctionCompilerState,
) -> Vec<String> {
next.compile_internal(
prefix + new,
require_grouping,
options,
global_state,
function_state,
)
}
fn combine_conditions_commands(conditions: Vec<String>, commands: Vec<String>) -> Vec<String> {
conditions
.into_iter()
.flat_map(|cond| commands.iter().map(move |cmd| cond.clone() + " " + cmd))
.collect()
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Condition { pub enum Condition {
Atom(String), Atom(String),
@ -30,11 +251,12 @@ pub enum Condition {
Or(Box<Condition>, Box<Condition>), Or(Box<Condition>, Box<Condition>),
} }
impl Condition { impl Condition {
pub fn normalize(self) -> Self { /// Normalize the condition.
pub fn normalize(&self) -> Self {
match self { match self {
Self::Atom(_) => self, Self::Atom(_) => self.clone(),
Self::Not(c) => match *c { Self::Not(c) => match *c.clone() {
Self::Atom(c) => Self::Not(Box::new(Self::Atom(c))), Self::Atom(c) => Self::Not(Box::new(Self::Atom(c.clone()))),
Self::Not(c) => c.normalize(), Self::Not(c) => c.normalize(),
Self::And(c1, c2) => ((!*c1).normalize()) | ((!*c2).normalize()), Self::And(c1, c2) => ((!*c1).normalize()) | ((!*c2).normalize()),
Self::Or(c1, c2) => ((!*c1).normalize()) & ((!*c2).normalize()), Self::Or(c1, c2) => ((!*c1).normalize()) & ((!*c2).normalize()),
@ -43,6 +265,42 @@ impl Condition {
Self::Or(c1, c2) => c1.normalize() | c2.normalize(), Self::Or(c1, c2) => c1.normalize() | c2.normalize(),
} }
} }
/// Compile the condition into a list of strings.
pub fn compile(
&self,
_options: &CompileOptions,
_global_state: &MutCompilerState,
_function_state: &MutFunctionCompilerState,
) -> Vec<String> {
match self.normalize() {
Self::Atom(a) => vec!["if ".to_string() + &a],
Self::Not(n) => match n.as_ref() {
Self::Atom(a) => vec!["unless ".to_string() + a],
_ => unreachable!("Cannot happen because of normalization"),
},
Self::And(c1, c2) => {
let c1 = c1.compile(_options, _global_state, _function_state);
let c2 = c2.compile(_options, _global_state, _function_state);
c1.into_iter()
.flat_map(|c1| c2.iter().map(move |c2| c1.clone() + " " + c2))
.collect()
}
Self::Or(c1, c2) => {
let mut c1 = c1.compile(_options, _global_state, _function_state);
let c2 = c2.compile(_options, _global_state, _function_state);
c1.extend(c2);
c1
}
}
}
}
impl From<&str> for Condition {
fn from(s: &str) -> Self {
Condition::Atom(s.to_string())
}
} }
impl Not for Condition { impl Not for Condition {

View File

@ -1,12 +1,15 @@
//! Represents a command that can be included in a function. //! Represents a command that can be included in a function.
mod execute; mod execute;
pub use execute::Execute;
pub use execute::{Condition, Execute};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::util::compile::{CompileOptions, MutCompilerState, MutFunctionCompilerState}; use crate::util::compile::{CompileOptions, MutCompilerState, MutFunctionCompilerState};
use super::Function;
/// Represents a command that can be included in a function. /// Represents a command that can be included in a function.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Command { pub enum Command {
@ -16,6 +19,8 @@ pub enum Command {
Debug(String), Debug(String),
/// Execute command /// Execute command
Execute(Execute), Execute(Execute),
/// Group of commands to be called instantly after each other
Group(Vec<Command>),
} }
impl Command { impl Command {
@ -30,14 +35,18 @@ impl Command {
options: &CompileOptions, options: &CompileOptions,
global_state: &MutCompilerState, global_state: &MutCompilerState,
function_state: &MutFunctionCompilerState, function_state: &MutFunctionCompilerState,
) -> String { ) -> Vec<String> {
let _ = options;
let _ = global_state;
let _ = function_state;
match self { match self {
Self::Raw(command) => command.clone(), Self::Raw(command) => vec![command.clone()],
Self::Debug(message) => compile_debug(message, options), Self::Debug(message) => compile_debug(message, options),
Self::Execute(_) => todo!(), Self::Execute(ex) => ex.compile(options, global_state, function_state),
Self::Group(commands) => {
// TODO: implement correctly
commands
.iter()
.flat_map(|c| c.compile(options, global_state, function_state))
.collect()
}
} }
} }
} }
@ -47,14 +56,24 @@ impl From<&str> for Command {
Self::raw(command) Self::raw(command)
} }
} }
impl From<&Function> for Command {
fn compile_debug(message: &str, option: &CompileOptions) -> String { fn from(value: &Function) -> Self {
if option.debug { Self::Raw(format!("function {}:{}", value.namespace(), value.name()))
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"}}]"#, }
message impl From<&mut Function> for Command {
) fn from(value: &mut Function) -> Self {
} else { Self::Raw(format!("function {}:{}", value.namespace(), value.name()))
String::new() }
}
fn compile_debug(message: &str, option: &CompileOptions) -> Vec<String> {
if option.debug {
vec![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"}}]"#,
message
)]
} else {
Vec::new()
} }
} }

View File

@ -2,6 +2,7 @@
use std::sync::Mutex; use std::sync::Mutex;
use getset::Getters;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@ -12,20 +13,28 @@ use crate::{
use super::command::Command; use super::command::Command;
/// Function that can be called by a command /// Function that can be called by a command
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, Getters)]
pub struct Function { pub struct Function {
commands: Vec<Command>, commands: Vec<Command>,
/// Name of the function
#[get = "pub"]
name: String,
/// Namespace of the function
#[get = "pub"]
namespace: String,
} }
impl Function { impl Function {
/// Create a new function. pub(in crate::datapack) fn new(namespace: &str, name: &str) -> Self {
pub fn new() -> Self { Self {
Self::default() commands: Vec::new(),
name: name.to_string(),
namespace: namespace.to_string(),
}
} }
/// Add a command to the function. /// Add a command to the function.
pub fn add_command(&mut self, command: Command) { pub fn add_command(&mut self, command: impl Into<Command>) {
self.commands.push(command); self.commands.push(command.into());
} }
/// Get the commands of the function. /// Get the commands of the function.
@ -40,7 +49,7 @@ impl Function {
let content = self let content = self
.commands .commands
.iter() .iter()
.map(|c| c.compile(options, state, &function_state)) .flat_map(|c| c.compile(options, state, &function_state))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
VFile::Text(content) VFile::Text(content)

View File

@ -4,7 +4,7 @@ mod command;
mod function; mod function;
mod namespace; mod namespace;
pub mod tag; pub mod tag;
pub use command::Command; pub use command::{Command, Condition, Execute};
pub use function::Function; pub use function::Function;
pub use namespace::Namespace; pub use namespace::Namespace;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -72,13 +72,16 @@ impl Datapack {
}) })
} }
/// Add a namespace to the datapack. /// Get a namespace by name.
pub fn add_namespace(&mut self, namespace: Namespace) { pub fn namespace(&self, name: &str) -> Option<&Namespace> {
if !namespace.get_main_function().get_commands().is_empty() { self.namespaces.get(name)
self.add_tick(&format!("{}:main", namespace.get_name())); }
}
/// Butably get a namespace by name or create a new one if it doesn't exist.
pub fn namespace_mut(&mut self, name: &str) -> &mut Namespace {
self.namespaces self.namespaces
.insert(namespace.get_name().to_string(), namespace); .entry(name.to_string())
.or_insert_with(|| Namespace::new(name))
} }
/// Add a function to the tick function list. /// Add a function to the tick function list.

View File

@ -21,7 +21,7 @@ pub struct Namespace {
impl Namespace { impl Namespace {
/// Create a new namespace. /// Create a new namespace.
pub fn new(name: &str) -> Self { pub(in crate::datapack) fn new(name: &str) -> Self {
Self { Self {
name: name.to_string(), name: name.to_string(),
functions: HashMap::new(), functions: HashMap::new(),
@ -54,9 +54,16 @@ impl Namespace {
&self.tags &self.tags
} }
/// Add a function to the namespace. /// Get a function by name.
pub fn add_function(&mut self, name: &str, function: Function) { pub fn function(&self, name: &str) -> Option<&Function> {
self.functions.insert(name.to_string(), function); self.functions.get(name)
}
/// Mutably get a function by name or create a new one if it doesn't exist.
pub fn function_mut(&mut self, name: &str) -> &mut Function {
self.functions
.entry(name.to_string())
.or_insert_with(|| Function::new(&self.name, name))
} }
/// Add a tag to the namespace. /// Add a tag to the namespace.