From d9e3184a0ee17b2b95f12614971975428ebec381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:50:00 +0200 Subject: [PATCH] implement validate function for checking the pack format compatibility --- CHANGELOG.md | 1 + src/datapack/command/execute.rs | 31 ++++++- src/datapack/command/mod.rs | 140 +++++++++++++++++++++++++++++++- src/datapack/function.rs | 8 ++ src/datapack/mod.rs | 22 +++++ src/datapack/namespace.rs | 17 +++- src/datapack/tag.rs | 10 +-- src/util/compile.rs | 30 ++++++- 8 files changed, 240 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3885f..5fc3e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Execute - Debug - Group +- Validate function for checking pack format compatibility - Virtual file system ### Changed diff --git a/src/datapack/command/execute.rs b/src/datapack/command/execute.rs index 799c9d2..12ccd97 100644 --- a/src/datapack/command/execute.rs +++ b/src/datapack/command/execute.rs @@ -1,4 +1,4 @@ -use std::ops::{BitAnd, BitOr, Not}; +use std::ops::{BitAnd, BitOr, Not, RangeInclusive}; use chksum_md5 as md5; @@ -164,12 +164,39 @@ impl Execute { Self::Runs(..) => "runs", } } + + /// Check whether the execute command is valid with the given pack format. + #[must_use] + pub fn validate(&self, pack_formats: &RangeInclusive) -> bool { + match self { + Self::Run(cmd) => cmd.validate(pack_formats), + Self::Runs(cmds) => cmds.iter().all(|cmd| cmd.validate(pack_formats)), + Self::Facing(_, next) + | Self::Store(_, next) + | Self::Positioned(_, next) + | Self::Rotated(_, next) + | Self::In(_, next) + | Self::As(_, next) + | Self::At(_, next) + | Self::AsAt(_, next) + | Self::Align(_, next) + | Self::Anchored(_, next) => pack_formats.start() >= &4 && next.validate(pack_formats), + Self::If(_, next, el) => { + pack_formats.start() >= &4 + && next.validate(pack_formats) + && el.as_deref().map_or(true, |el| el.validate(pack_formats)) + } + Self::Summon(_, next) | Self::On(_, next) => { + pack_formats.start() >= &12 && next.validate(pack_formats) + } + } + } } /// 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 fn map_run_cmd(cmd: String, prefix: &str) -> (bool, String) { - if cmd.starts_with('#') { + if cmd.starts_with('#') || cmd.is_empty() || cmd.chars().all(char::is_whitespace) { (false, cmd) } else { (true, prefix.to_string() + "run " + &cmd) diff --git a/src/datapack/command/mod.rs b/src/datapack/command/mod.rs index 38a0c29..39e5243 100644 --- a/src/datapack/command/mod.rs +++ b/src/datapack/command/mod.rs @@ -1,12 +1,17 @@ //! Represents a command that can be included in a function. mod execute; +use std::{collections::HashMap, ops::RangeInclusive, sync::OnceLock}; + pub use execute::{Condition, Execute}; use chksum_md5 as md5; use super::Function; -use crate::util::compile::{CompileOptions, FunctionCompilerState, MutCompilerState}; +use crate::{ + prelude::Datapack, + util::compile::{CompileOptions, FunctionCompilerState, MutCompilerState}, +}; /// Represents a command that can be included in a function. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -58,6 +63,16 @@ impl Command { Self::Group(_) => 1, } } + + /// Check whether the command is valid with the given pack format. + #[must_use] + pub fn validate(&self, pack_formats: &RangeInclusive) -> bool { + match self { + Self::Comment(_) | Self::Debug(_) | Self::Group(_) => true, + Self::Raw(cmd) => validate_raw_cmd(cmd, pack_formats), + Self::Execute(ex) => ex.validate(pack_formats), + } + } } impl From<&str> for Command { @@ -128,3 +143,126 @@ fn compile_group( .collect::>() } } + +#[allow(clippy::too_many_lines)] +fn validate_raw_cmd(cmd: &str, pack_formats: &RangeInclusive) -> bool { + static CMD_FORMATS: OnceLock>> = OnceLock::new(); + let cmd_formats = CMD_FORMATS.get_or_init(|| { + const LATEST: u8 = Datapack::LATEST_FORMAT; + const ANY: RangeInclusive = 0..=LATEST; + const fn to(to: u8) -> RangeInclusive { + 0..=to + } + const fn from(from: u8) -> RangeInclusive { + from..=LATEST + } + + const ANY_CMD: &[&str] = &[ + "advancement", + "ban", + "ban-ip", + "banlist", + "clear", + "clone", + "debug", + "defaultgamemode", + "deop", + "difficulty", + "effect", + "enchant", + "execute", + "experience", + "fill", + "gamemode", + "gamerule", + "give", + "help", + "kick", + "kill", + "list", + "locate", + "me", + "msg", + "op", + "pardon", + "pardon-ip", + "particle", + "playsound", + "publish", + "recipe", + "reload", + "save-all", + "save-off", + "save-on", + "say", + "scoreboard", + "seed", + "setblock", + "setidletimeout", + "setworldspawn", + "spawnpoint", + "spreadplayers", + "stop", + "stopsound", + "summon", + "teleport", + "tell", + "tellraw", + "time", + "title", + "tp", + "trigger", + "w", + "weather", + "whitelist", + "worldborder", + "xp", + ]; + + let mut map = HashMap::new(); + + for cmd in ANY_CMD { + map.insert(*cmd, ANY); + } + map.insert("attribute", from(6)); + map.insert("bossbar", from(4)); + map.insert("damage", from(12)); + map.insert("data", from(4)); + map.insert("datapack", from(4)); + map.insert("fillbiome", from(12)); + map.insert("forceload", from(4)); + map.insert("function", from(4)); + map.insert("replaceitem", to(6)); + map.insert("item", from(7)); + map.insert("jfr", from(8)); + map.insert("loot", from(4)); + map.insert("perf", from(7)); + map.insert("place", from(10)); + map.insert("placefeature", 9..=9); + map.insert("random", from(18)); + map.insert("return", from(15)); + map.insert("ride", from(12)); + map.insert("schedule", from(4)); + map.insert("spectate", from(5)); + map.insert("tag", from(4)); + map.insert("team", from(4)); + map.insert("teammsg", from(4)); + map.insert("tick", from(22)); + map.insert("tm", from(4)); + map.insert("transfer", from(41)); + + map + }); + + cmd.split_ascii_whitespace().next().map_or(true, |cmd| { + cmd_formats.get(cmd).map_or(true, |range| { + let start_cmd = range.start(); + let end_cmd = range.end(); + + let start_pack = pack_formats.start(); + let end_pack = pack_formats.end(); + + start_cmd <= start_pack && end_cmd >= end_pack + }) + }) +} diff --git a/src/datapack/function.rs b/src/datapack/function.rs index 1905995..2775e1c 100644 --- a/src/datapack/function.rs +++ b/src/datapack/function.rs @@ -1,5 +1,7 @@ //! Function struct and implementation +use std::ops::RangeInclusive; + use getset::Getters; use crate::{ @@ -65,4 +67,10 @@ impl Function { .join("\n"); VFile::Text(content) } + + // Check whether the function is valid with the given pack format. + #[must_use] + pub fn validate(&self, pack_formats: &RangeInclusive) -> bool { + self.commands.iter().all(|c| c.validate(pack_formats)) + } } diff --git a/src/datapack/mod.rs b/src/datapack/mod.rs index 7086880..3e7e3d3 100644 --- a/src/datapack/mod.rs +++ b/src/datapack/mod.rs @@ -28,6 +28,8 @@ pub struct Datapack { } impl Datapack { + pub(crate) const LATEST_FORMAT: u8 = 48; + /// Create a new Minecraft datapack. #[must_use] pub fn new(pack_format: u8) -> Self { @@ -105,9 +107,17 @@ impl Datapack { } /// Compile the pack into a virtual folder. + /// + /// The pack format in the compile options will be overridden by the pack format of the datapack. #[must_use] #[tracing::instrument(level = "debug", skip(self))] pub fn compile(&self, options: &CompileOptions) -> VFolder { + let pack_formats = self + .supported_formats + .clone() + .unwrap_or(self.pack_format..=self.pack_format); + let options = &options.clone().with_pack_formats(pack_formats); + tracing::debug!("Compiling datapack: {:?}", self); let compiler_state = Mutex::new(CompilerState::default()); @@ -126,6 +136,18 @@ impl Datapack { root_folder.add_existing_folder("data", data_folder); root_folder } + + /// Check whether the datapack is valid with the given pack format. + #[must_use] + pub fn validate(&self) -> bool { + let pack_formats = self + .supported_formats + .clone() + .unwrap_or(self.pack_format..=self.pack_format); + self.namespaces + .values() + .all(|namespace| namespace.validate(&pack_formats)) + } } fn generate_mcmeta(dp: &Datapack, _options: &CompileOptions, _state: &MutCompilerState) -> VFile { diff --git a/src/datapack/namespace.rs b/src/datapack/namespace.rs index 58cd10f..cb634e7 100644 --- a/src/datapack/namespace.rs +++ b/src/datapack/namespace.rs @@ -12,7 +12,10 @@ use super::{ function::Function, tag::{Tag, TagType}, }; -use std::collections::{HashMap, VecDeque}; +use std::{ + collections::{HashMap, VecDeque}, + ops::RangeInclusive, +}; /// Namespace of a datapack #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -75,8 +78,8 @@ impl Namespace { #[must_use] pub fn tag_mut(&mut self, name: &str, tag_type: TagType) -> &mut Tag { self.tags - .entry((name.to_string(), tag_type.clone())) - .or_insert_with(|| Tag::new(tag_type, false)) + .entry((name.to_string(), tag_type)) + .or_insert_with(|| Tag::new(false)) } /// Compile the namespace into a virtual folder. @@ -117,4 +120,12 @@ impl Namespace { root_folder } + + /// Check whether the namespace is valid with the given pack format. + #[must_use] + pub fn validate(&self, pack_formats: &RangeInclusive) -> bool { + self.functions + .values() + .all(|function| function.validate(pack_formats)) + } } diff --git a/src/datapack/tag.rs b/src/datapack/tag.rs index d3254ce..190a03c 100644 --- a/src/datapack/tag.rs +++ b/src/datapack/tag.rs @@ -9,27 +9,19 @@ use crate::{ #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct Tag { - r#type: TagType, replace: bool, values: Vec, } impl Tag { /// Create a new tag. #[must_use] - pub fn new(r#type: TagType, replace: bool) -> Self { + pub fn new(replace: bool) -> Self { Self { - r#type, replace, values: Vec::new(), } } - /// Get the type of the tag. - #[must_use] - pub fn get_type(&self) -> &TagType { - &self.r#type - } - /// Get whether the tag should replace existing values. #[must_use] pub fn get_replace(&self) -> bool { diff --git a/src/util/compile.rs b/src/util/compile.rs index e075c58..cb39275 100644 --- a/src/util/compile.rs +++ b/src/util/compile.rs @@ -1,10 +1,10 @@ //! Compile options for the compiler. -use std::sync::Mutex; +use std::{ops::RangeInclusive, sync::Mutex}; use getset::Getters; -use crate::datapack::Function; +use crate::{datapack::Function, prelude::Datapack}; use super::extendable_queue::ExtendableQueue; @@ -14,12 +14,34 @@ use super::extendable_queue::ExtendableQueue; #[derive(Debug, Clone)] pub struct CompileOptions { /// Whether to compile in debug mode. - pub debug: bool, + pub(crate) debug: bool, + + pub(crate) pack_formats: RangeInclusive, +} + +impl CompileOptions { + /// Set whether to compile in debug mode. + #[must_use] + pub fn with_debug(self, debug: bool) -> Self { + Self { debug, ..self } + } + + /// Set the pack format of the datapack. + #[must_use] + pub fn with_pack_formats(self, pack_formats: RangeInclusive) -> Self { + Self { + pack_formats, + ..self + } + } } impl Default for CompileOptions { fn default() -> Self { - Self { debug: true } + Self { + debug: true, + pack_formats: Datapack::LATEST_FORMAT..=Datapack::LATEST_FORMAT, + } } }