implement validate function for checking the pack format compatibility

This commit is contained in:
Moritz Hölting 2024-06-18 20:50:00 +02:00
parent cf62725a5e
commit d9e3184a0e
8 changed files with 240 additions and 19 deletions

View File

@ -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

View File

@ -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)

View File

@ -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
})
})
}

View File

@ -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))
}
}

View File

@ -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 {

View File

@ -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))
}
}

View File

@ -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 {

View File

@ -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,
}
}
}