mcfunction-debugger 2.0.0

A debugger for Minecraft's *.mcfunction files that does not require any Minecraft mods
Documentation
// Mcfunction-Debugger is a debugger for Minecraft's *.mcfunction files that does not require any
// Minecraft mods.
//
// © Copyright (C) 2021-2024 Adrodoc <adrodoc55@googlemail.com> & Skagaros <skagaros@gmail.com>
//
// This file is part of Mcfunction-Debugger.
//
// Mcfunction-Debugger is free software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// Mcfunction-Debugger is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Mcfunction-Debugger.
// If not, see <http://www.gnu.org/licenses/>.

pub mod brigadier;
pub mod minecraft;

use self::{
    brigadier::BrigadierStringType,
    minecraft::{
        coordinate::{MinecraftBlockPos, MinecraftRotation, MinecraftVec3},
        entity::MinecraftSelector,
        nbt::MinecraftNbtPath,
        range::MinecraftRange,
        resource::{MinecraftResource, MinecraftResourceLocation, MinecraftResourceOrTag},
        MinecraftDimension, MinecraftEntityAnchor, MinecraftFunction, MinecraftHeightmap,
        MinecraftMessage, MinecraftObjective, MinecraftObjectiveCriteria, MinecraftOperation,
        MinecraftScoreHolder, MinecraftScoreboardSlot, MinecraftSwizzle, MinecraftTeam,
        MinecraftTime,
    },
};
use crate::{
    generator::parser::command::{
        argument::minecraft::{block::MinecraftBlockPredicate, entity::MinecraftEntity},
        resource_location::ResourceLocation,
    },
    utils::Map0,
};
use serde::{Deserialize, Serialize};
use std::{str::FromStr, usize};

trait ArgumentValue<'l>: Sized {
    fn parse(string: &'l str) -> Result<(Self, usize), String>;
}

trait ArgumentValueEnum: FromStr + Sized {}

impl<E: ArgumentValueEnum> ArgumentValue<'_> for E {
    fn parse(string: &str) -> Result<(Self, usize), String> {
        let (value, len) = brigadier::parse_unquoted_string(string);
        let value = Self::from_str(value).map_err(|_| format!("Invalid value \"{}\"", value))?;
        Ok((value, len))
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum Argument<'l> {
    BrigadierDouble(f64),
    BrigadierInteger(i32),
    BrigadierString(&'l str),
    MinecraftBlockPos(MinecraftBlockPos),
    MinecraftBlockPredicate(MinecraftBlockPredicate<'l>),
    MinecraftDimension(MinecraftDimension<'l>),
    MinecraftEntity(MinecraftEntity<'l>),
    MinecraftEntityAnchor(MinecraftEntityAnchor),
    MinecraftFunction(MinecraftFunction<'l>),
    MinecraftIntRange(MinecraftRange<i32>),
    MinecraftHeightmap(MinecraftHeightmap),
    MinecraftMessage(MinecraftMessage<'l>),
    MinecraftNbtPath(MinecraftNbtPath<'l>),
    MinecraftObjective(MinecraftObjective<'l>),
    MinecraftObjectiveCriteria(MinecraftObjectiveCriteria<'l>),
    MinecraftOperation(MinecraftOperation),
    MinecraftResource(MinecraftResource<'l>),
    MinecraftResourceLocation(MinecraftResourceLocation<'l>),
    MinecraftResourceOrTag(MinecraftResourceOrTag<'l>),
    MinecraftRotation(MinecraftRotation),
    MinecraftScoreHolder(MinecraftScoreHolder<'l>),
    MinecraftScoreboardSlot(MinecraftScoreboardSlot<'l>),
    MinecraftSwizzle(MinecraftSwizzle),
    MinecraftTeam(MinecraftTeam<'l>),
    MinecraftTime(MinecraftTime),
    MinecraftVec3(MinecraftVec3),
    Unknown(&'l str),
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(tag = "parser", content = "properties")]
pub enum ArgumentParser {
    #[serde(rename = "brigadier:bool")]
    BrigadierBool,
    #[serde(rename = "brigadier:double")]
    BrigadierDouble(Option<BrigadierDoubleProperties>),
    #[serde(rename = "brigadier:float")]
    BrigadierFloat(Option<BrigadierFloatProperties>),
    #[serde(rename = "brigadier:integer")]
    BrigadierInteger(Option<BrigadierIntegerProperties>),
    #[serde(rename = "brigadier:string")]
    BrigadierString {
        #[serde(rename = "type")]
        type_: BrigadierStringType,
    },
    #[serde(rename = "minecraft:angle")]
    MinecraftAngle,
    #[serde(rename = "minecraft:block_pos")]
    MinecraftBlockPos,
    #[serde(rename = "minecraft:block_predicate")]
    MinecraftBlockPredicate,
    #[serde(rename = "minecraft:block_state")]
    MinecraftBlockState,
    #[serde(rename = "minecraft:color")]
    MinecraftColor,
    #[serde(rename = "minecraft:column_pos")]
    MinecraftColumnPos,
    #[serde(rename = "minecraft:component")]
    MinecraftComponent,
    #[serde(rename = "minecraft:dimension")]
    MinecraftDimension,
    #[serde(rename = "minecraft:entity")]
    MinecraftEntity {
        #[serde(rename = "type")]
        type_: MinecraftEntityType,
        amount: MinecraftAmount,
    },
    #[serde(rename = "minecraft:entity_anchor")]
    MinecraftEntityAnchor,
    #[serde(rename = "minecraft:entity_summon")]
    MinecraftEntitySummon,
    #[serde(rename = "minecraft:function")]
    MinecraftFunction,
    #[serde(rename = "minecraft:game_profile")]
    MinecraftGameProfile,
    #[serde(rename = "minecraft:gamemode")]
    MinecraftGamemode,
    #[serde(rename = "minecraft:heightmap")]
    MinecraftHeightmap,
    #[serde(rename = "minecraft:int_range")]
    MinecraftIntRange,
    #[serde(rename = "minecraft:item_enchantment")]
    MinecraftItemEnchantment,
    #[serde(rename = "minecraft:item_predicate")]
    MinecraftItemPredicate,
    #[serde(rename = "minecraft:item_slot")]
    MinecraftItemSlot,
    #[serde(rename = "minecraft:item_stack")]
    MinecraftItemStack,
    #[serde(rename = "minecraft:message")]
    MinecraftMessage,
    #[serde(rename = "minecraft:mob_effect")]
    MinecraftMobEffect,
    #[serde(rename = "minecraft:nbt_compound_tag")]
    MinecraftNbtCompoundTag,
    #[serde(rename = "minecraft:nbt_path")]
    MinecraftNbtPath,
    #[serde(rename = "minecraft:nbt_tag")]
    MinecraftNbtTag,
    #[serde(rename = "minecraft:objective")]
    MinecraftObjective,
    #[serde(rename = "minecraft:objective_criteria")]
    MinecraftObjectiveCriteria,
    #[serde(rename = "minecraft:operation")]
    MinecraftOperation,
    #[serde(rename = "minecraft:particle")]
    MinecraftParticle,
    #[serde(rename = "minecraft:resource")]
    MinecraftResource { registry: ResourceLocation },
    #[serde(rename = "minecraft:resource_key")]
    MinecraftResourceKey { registry: ResourceLocation },
    #[serde(rename = "minecraft:resource_location")]
    MinecraftResourceLocation,
    #[serde(rename = "minecraft:resource_or_tag_key")]
    MinecraftResourceOrTagKey { registry: ResourceLocation },
    #[serde(rename = "minecraft:resource_or_tag")]
    MinecraftResourceOrTag { registry: ResourceLocation },
    #[serde(rename = "minecraft:rotation")]
    MinecraftRotation,
    #[serde(rename = "minecraft:score_holder")]
    MinecraftScoreHolder { amount: MinecraftAmount },
    #[serde(rename = "minecraft:scoreboard_slot")]
    MinecraftScoreboardSlot,
    #[serde(rename = "minecraft:swizzle")]
    MinecraftSwizzle,
    #[serde(rename = "minecraft:team")]
    MinecraftTeam,
    #[serde(rename = "minecraft:template_mirror")]
    MinecraftTemplateMirror,
    #[serde(rename = "minecraft:template_rotation")]
    MinecraftTemplateRotation,
    #[serde(rename = "minecraft:time")]
    MinecraftTime { min: i32 },
    #[serde(rename = "minecraft:uuid")]
    MinecraftUuid,
    #[serde(rename = "minecraft:vec2")]
    MinecraftVec2,
    #[serde(rename = "minecraft:vec3")]
    MinecraftVec3,
    #[serde(other)]
    Unknown,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct BrigadierDoubleProperties {
    pub min: Option<f64>,
    pub max: Option<f64>,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct BrigadierFloatProperties {
    pub min: Option<f32>,
    pub max: Option<f32>,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct BrigadierIntegerProperties {
    pub min: Option<i32>,
    pub max: Option<i32>,
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum MinecraftEntityType {
    Players,
    Entities,
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum MinecraftAmount {
    Single,
    Multiple,
}

impl ArgumentParser {
    fn name(&self) -> Option<String> {
        let a = serde_json::to_value(self).ok()?;
        a.as_object()?.get("parser")?.as_str().map(String::from)
    }

    pub fn parse<'l>(&self, s: &'l str) -> Result<(Argument<'l>, usize), String> {
        fn parse_argument<'l, A: ArgumentValue<'l>>(
            string: &'l str,
            argument_factory: impl Fn(A) -> Argument<'l>,
        ) -> Result<(Argument<'l>, usize), String> {
            Ok(A::parse(string)?.map0(argument_factory))
        }
        type A<'l> = Argument<'l>;
        match self {
            Self::BrigadierDouble(..) => Ok(brigadier::parse_double(s)?.map0(A::BrigadierDouble)),
            Self::BrigadierInteger(..) => {
                Ok(brigadier::parse_integer(s)?.map0(A::BrigadierInteger))
            }
            Self::BrigadierString { type_ } => {
                Ok(brigadier::parse_string(s, *type_)?.map0(A::BrigadierString))
            }
            Self::MinecraftBlockPos => parse_argument(s, A::MinecraftBlockPos),
            Self::MinecraftBlockPredicate => parse_argument(s, A::MinecraftBlockPredicate),
            Self::MinecraftDimension => parse_argument(s, A::MinecraftDimension),
            Self::MinecraftEntity { .. } => parse_argument(s, A::MinecraftEntity),
            Self::MinecraftEntityAnchor => parse_argument(s, A::MinecraftEntityAnchor),
            Self::MinecraftFunction => parse_argument(s, A::MinecraftFunction),
            Self::MinecraftIntRange => parse_argument(s, A::MinecraftIntRange),
            Self::MinecraftHeightmap => parse_argument(s, A::MinecraftHeightmap),
            Self::MinecraftMessage => parse_argument(s, A::MinecraftMessage),
            Self::MinecraftNbtPath => parse_argument(s, A::MinecraftNbtPath),
            Self::MinecraftObjective => parse_argument(s, A::MinecraftObjective),
            Self::MinecraftObjectiveCriteria => parse_argument(s, A::MinecraftObjectiveCriteria),
            Self::MinecraftOperation => parse_argument(s, A::MinecraftOperation),
            Self::MinecraftResource { .. } => parse_argument(s, A::MinecraftResource),
            Self::MinecraftResourceLocation => parse_argument(s, A::MinecraftResourceLocation),
            Self::MinecraftResourceOrTag { .. } => parse_argument(s, A::MinecraftResourceOrTag),
            Self::MinecraftRotation => parse_argument(s, A::MinecraftRotation),
            Self::MinecraftScoreHolder { .. } => parse_argument(s, A::MinecraftScoreHolder),
            Self::MinecraftScoreboardSlot => parse_argument(s, A::MinecraftScoreboardSlot),
            Self::MinecraftSwizzle => parse_argument(s, A::MinecraftSwizzle),
            Self::MinecraftTime { .. } => parse_argument(s, A::MinecraftTime),
            Self::MinecraftTeam => parse_argument(s, A::MinecraftTeam),
            Self::MinecraftVec3 => parse_argument(s, A::MinecraftVec3),
            Self::Unknown => Ok(parse_unknown(s)?.map0(A::Unknown)),
            _ => Err(format!(
                "Unsupported argument type: {}",
                self.name().unwrap_or_default()
            )),
        }
    }
}

fn parse_unknown(string: &str) -> Result<(&str, usize), String> {
    // Best effort
    let len = string.find(' ').unwrap_or(string.len());
    Ok((&string[..len], len))
}