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