implement validate function for checking the pack format compatibility
This commit is contained in:
		
							parent
							
								
									cf62725a5e
								
							
						
					
					
						commit
						d9e3184a0e
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<u8>) -> 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<u8>) -> 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::<Vec<_>>()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::too_many_lines)]
 | 
			
		||||
fn validate_raw_cmd(cmd: &str, pack_formats: &RangeInclusive<u8>) -> bool {
 | 
			
		||||
    static CMD_FORMATS: OnceLock<HashMap<&str, RangeInclusive<u8>>> = OnceLock::new();
 | 
			
		||||
    let cmd_formats = CMD_FORMATS.get_or_init(|| {
 | 
			
		||||
        const LATEST: u8 = Datapack::LATEST_FORMAT;
 | 
			
		||||
        const ANY: RangeInclusive<u8> = 0..=LATEST;
 | 
			
		||||
        const fn to(to: u8) -> RangeInclusive<u8> {
 | 
			
		||||
            0..=to
 | 
			
		||||
        }
 | 
			
		||||
        const fn from(from: u8) -> RangeInclusive<u8> {
 | 
			
		||||
            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
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<u8>) -> bool {
 | 
			
		||||
        self.commands.iter().all(|c| c.validate(pack_formats))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<u8>) -> bool {
 | 
			
		||||
        self.functions
 | 
			
		||||
            .values()
 | 
			
		||||
            .all(|function| function.validate(pack_formats))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<TagValue>,
 | 
			
		||||
}
 | 
			
		||||
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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<u8>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<u8>) -> 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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue