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
|
- Execute
|
||||||
- Debug
|
- Debug
|
||||||
- Group
|
- Group
|
||||||
|
- Validate function for checking pack format compatibility
|
||||||
- Virtual file system
|
- Virtual file system
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::ops::{BitAnd, BitOr, Not};
|
use std::ops::{BitAnd, BitOr, Not, RangeInclusive};
|
||||||
|
|
||||||
use chksum_md5 as md5;
|
use chksum_md5 as md5;
|
||||||
|
|
||||||
|
@ -164,12 +164,39 @@ impl Execute {
|
||||||
Self::Runs(..) => "runs",
|
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
|
/// 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(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)
|
(false, cmd)
|
||||||
} else {
|
} else {
|
||||||
(true, prefix.to_string() + "run " + &cmd)
|
(true, prefix.to_string() + "run " + &cmd)
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
//! 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};
|
||||||
|
|
||||||
pub use execute::{Condition, Execute};
|
pub use execute::{Condition, Execute};
|
||||||
|
|
||||||
use chksum_md5 as md5;
|
use chksum_md5 as md5;
|
||||||
|
|
||||||
use super::Function;
|
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.
|
/// Represents a command that can be included in a function.
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -58,6 +63,16 @@ impl Command {
|
||||||
Self::Group(_) => 1,
|
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 {
|
impl From<&str> for Command {
|
||||||
|
@ -128,3 +143,126 @@ fn compile_group(
|
||||||
.collect::<Vec<_>>()
|
.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
|
//! Function struct and implementation
|
||||||
|
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -65,4 +67,10 @@ impl Function {
|
||||||
.join("\n");
|
.join("\n");
|
||||||
VFile::Text(content)
|
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 {
|
impl Datapack {
|
||||||
|
pub(crate) const LATEST_FORMAT: u8 = 48;
|
||||||
|
|
||||||
/// Create a new Minecraft datapack.
|
/// Create a new Minecraft datapack.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(pack_format: u8) -> Self {
|
pub fn new(pack_format: u8) -> Self {
|
||||||
|
@ -105,9 +107,17 @@ impl Datapack {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile the pack into a virtual folder.
|
/// 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]
|
#[must_use]
|
||||||
#[tracing::instrument(level = "debug", skip(self))]
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
pub fn compile(&self, options: &CompileOptions) -> VFolder {
|
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);
|
tracing::debug!("Compiling datapack: {:?}", self);
|
||||||
|
|
||||||
let compiler_state = Mutex::new(CompilerState::default());
|
let compiler_state = Mutex::new(CompilerState::default());
|
||||||
|
@ -126,6 +136,18 @@ impl Datapack {
|
||||||
root_folder.add_existing_folder("data", data_folder);
|
root_folder.add_existing_folder("data", data_folder);
|
||||||
root_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 {
|
fn generate_mcmeta(dp: &Datapack, _options: &CompileOptions, _state: &MutCompilerState) -> VFile {
|
||||||
|
|
|
@ -12,7 +12,10 @@ use super::{
|
||||||
function::Function,
|
function::Function,
|
||||||
tag::{Tag, TagType},
|
tag::{Tag, TagType},
|
||||||
};
|
};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
ops::RangeInclusive,
|
||||||
|
};
|
||||||
|
|
||||||
/// Namespace of a datapack
|
/// Namespace of a datapack
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -75,8 +78,8 @@ impl Namespace {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn tag_mut(&mut self, name: &str, tag_type: TagType) -> &mut Tag {
|
pub fn tag_mut(&mut self, name: &str, tag_type: TagType) -> &mut Tag {
|
||||||
self.tags
|
self.tags
|
||||||
.entry((name.to_string(), tag_type.clone()))
|
.entry((name.to_string(), tag_type))
|
||||||
.or_insert_with(|| Tag::new(tag_type, false))
|
.or_insert_with(|| Tag::new(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile the namespace into a virtual folder.
|
/// Compile the namespace into a virtual folder.
|
||||||
|
@ -117,4 +120,12 @@ impl Namespace {
|
||||||
|
|
||||||
root_folder
|
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))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
r#type: TagType,
|
|
||||||
replace: bool,
|
replace: bool,
|
||||||
values: Vec<TagValue>,
|
values: Vec<TagValue>,
|
||||||
}
|
}
|
||||||
impl Tag {
|
impl Tag {
|
||||||
/// Create a new tag.
|
/// Create a new tag.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(r#type: TagType, replace: bool) -> Self {
|
pub fn new(replace: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
r#type,
|
|
||||||
replace,
|
replace,
|
||||||
values: Vec::new(),
|
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.
|
/// Get whether the tag should replace existing values.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_replace(&self) -> bool {
|
pub fn get_replace(&self) -> bool {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
//! Compile options for the compiler.
|
//! Compile options for the compiler.
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::{ops::RangeInclusive, sync::Mutex};
|
||||||
|
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
|
||||||
use crate::datapack::Function;
|
use crate::{datapack::Function, prelude::Datapack};
|
||||||
|
|
||||||
use super::extendable_queue::ExtendableQueue;
|
use super::extendable_queue::ExtendableQueue;
|
||||||
|
|
||||||
|
@ -14,12 +14,34 @@ use super::extendable_queue::ExtendableQueue;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CompileOptions {
|
pub struct CompileOptions {
|
||||||
/// Whether to compile in debug mode.
|
/// 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 {
|
impl Default for CompileOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { debug: true }
|
Self {
|
||||||
|
debug: true,
|
||||||
|
pack_formats: Datapack::LATEST_FORMAT..=Datapack::LATEST_FORMAT,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue