File tag.h¶
File List > endstone > nbt > tag.h
Go to the documentation of this file
// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <regex>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "endstone/detail.h"
#include "endstone/nbt/array.h"
#include "endstone/nbt/compound.h"
#include "endstone/nbt/list.h"
#include "endstone/nbt/type.h"
#include "endstone/nbt/value.h"
namespace endstone {
using ByteTag = nbt::ValueTag<std::uint8_t>;
using ShortTag = nbt::ValueTag<std::int16_t>;
using IntTag = nbt::ValueTag<std::int32_t>;
using LongTag = nbt::ValueTag<std::int64_t>;
using FloatTag = nbt::ValueTag<float>;
using DoubleTag = nbt::ValueTag<double>;
using ByteArrayTag = nbt::ArrayTag<std::uint8_t>;
using StringTag = nbt::ValueTag<std::string>;
using IntArrayTag = nbt::ArrayTag<std::int32_t>;
namespace nbt {
class Tag {
public:
using Storage = std::variant<std::monostate, ByteTag, ShortTag, IntTag, LongTag, FloatTag, DoubleTag, StringTag,
ByteArrayTag, IntArrayTag, ListTag, CompoundTag>;
Tag() noexcept : storage_(std::monostate()) {}
Tag(const ByteTag &v) : storage_(v) {}
Tag(ByteTag &&v) : storage_(std::move(v)) {}
Tag(const ShortTag &v) : storage_(v) {}
Tag(ShortTag &&v) : storage_(std::move(v)) {}
Tag(const IntTag &v) : storage_(v) {}
Tag(IntTag &&v) : storage_(std::move(v)) {}
Tag(const LongTag &v) : storage_(v) {}
Tag(LongTag &&v) : storage_(std::move(v)) {}
Tag(const FloatTag &v) : storage_(v) {}
Tag(FloatTag &&v) : storage_(std::move(v)) {}
Tag(const DoubleTag &v) : storage_(v) {}
Tag(DoubleTag &&v) : storage_(std::move(v)) {}
Tag(const StringTag &v) : storage_(v) {}
Tag(StringTag &&v) : storage_(std::move(v)) {}
Tag(const ByteArrayTag &v) : storage_(v) {}
Tag(ByteArrayTag &&v) : storage_(std::move(v)) {}
Tag(const IntArrayTag &v) : storage_(v) {}
Tag(IntArrayTag &&v) : storage_(std::move(v)) {}
Tag(const ListTag &v) : storage_(v) {}
Tag(ListTag &&v) : storage_(std::move(v)) {}
Tag(const CompoundTag &v) : storage_(v) {}
Tag(CompoundTag &&v) : storage_(std::move(v)) {}
Type type() const noexcept
{
switch (storage_.index()) {
case 0:
return Type::End;
case 1:
return Type::Byte;
case 2:
return Type::Short;
case 3:
return Type::Int;
case 4:
return Type::Long;
case 5:
return Type::Float;
case 6:
return Type::Double;
case 7:
return Type::String;
case 8:
return Type::ByteArray;
case 9:
return Type::IntArray;
case 10:
return Type::List;
case 11:
return Type::Compound;
default:
return Type::End;
}
}
[[nodiscard]] std::size_t size() const noexcept
{
return std::visit(
[](auto &&arg) -> std::size_t {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>) {
return 0;
}
else if constexpr (std::is_integral_v<typename T::value_type> ||
std::is_floating_point_v<typename T::value_type>) {
return 1;
}
else if constexpr (std::is_same_v<T, StringTag>) {
return arg.value().size();
}
else {
return arg.size();
}
},
storage_);
}
[[nodiscard]] bool empty() const noexcept
{
return size() == 0;
}
Tag &operator[](const std::string &key)
{
if (std::holds_alternative<std::monostate>(storage_)) {
storage_.emplace<CompoundTag>();
}
const auto comp = std::get_if<CompoundTag>(&storage_);
if (!comp) {
throw std::runtime_error("Tag::operator[](key) requires CompoundTag");
}
return (*comp)[key];
}
Tag &operator[](std::size_t index)
{
if (std::holds_alternative<std::monostate>(storage_)) {
storage_.emplace<ListTag>();
}
const auto list = std::get_if<ListTag>(&storage_);
if (!list) {
throw std::runtime_error("Tag::operator[](index) requires ListTag");
}
return (*list)[index];
}
Tag &at(const std::string &key)
{
if (std::holds_alternative<std::monostate>(storage_)) {
storage_.emplace<CompoundTag>();
}
const auto comp = std::get_if<CompoundTag>(&storage_);
if (!comp) {
throw std::runtime_error("Tag::at(key) const requires CompoundTag");
}
return comp->at(key);
}
[[nodiscard]] const Tag &at(const std::string &key) const
{
const auto comp = std::get_if<CompoundTag>(&storage_);
if (!comp) {
throw std::runtime_error("Tag::at(key) const requires CompoundTag");
}
return comp->at(key);
}
Tag &at(std::size_t index)
{
if (std::holds_alternative<std::monostate>(storage_)) {
storage_.emplace<ListTag>();
}
const auto list = std::get_if<ListTag>(&storage_);
if (!list) {
throw std::runtime_error("Tag::at(index) requires ListTag");
}
return list->at(index);
}
[[nodiscard]] const Tag &at(std::size_t index) const
{
const auto list = std::get_if<ListTag>(&storage_);
if (!list) {
throw std::runtime_error("Tag::at(index) requires ListTag");
}
return list->at(index);
}
[[nodiscard]] bool contains(const std::string &key) const noexcept
{
const auto comp = std::get_if<CompoundTag>(&storage_);
if (!comp) {
return false;
}
return comp->contains(key);
}
template <class... Args>
ListTag &emplace_back(Args &&...args)
{
if (std::holds_alternative<std::monostate>(storage_)) {
storage_.emplace<ListTag>();
}
auto list = std::get_if<ListTag>(&storage_);
if (!list) {
throw std::runtime_error("Tag::emplace_back requires ListTag");
}
list->emplace_back(std::forward<Args>(args)...);
return *list;
}
template <class... Args>
std::pair<CompoundTag::iterator, bool> emplace(Args &&...args)
{
if (std::holds_alternative<std::monostate>(storage_)) {
storage_.emplace<CompoundTag>();
}
auto comp = std::get_if<CompoundTag>(&storage_);
if (!comp) {
throw std::runtime_error("Tag::emplace requires CompoundTag");
}
return comp->emplace(std::forward<Args>(args)...);
}
template <typename T>
T &get()
{
if (auto p = std::get_if<T>(&storage_)) {
return *p;
}
throw std::runtime_error("Tag::get<T>() kind mismatch");
}
template <typename T>
const T &get() const
{
if (auto p = std::get_if<T>(&storage_)) {
return *p;
}
throw std::runtime_error("Tag::get<T>() kind mismatch");
}
template <typename T>
T *get_if() noexcept
{
if (auto p = std::get_if<T>(&storage_)) {
return p;
}
return nullptr;
}
template <typename T>
const T *get_if() const noexcept
{
return std::get_if<T>(&storage_);
}
template <typename Fn>
decltype(auto) visit(Fn &&visitor) const &
{
return std::visit(visitor, storage_);
}
template <typename Fn>
decltype(auto) visit(Fn &&visitor) &
{
return std::visit(visitor, storage_);
}
friend bool operator==(const Tag &a, const Tag &b) noexcept
{
return a.storage_ == b.storage_;
}
friend bool operator!=(const Tag &a, const Tag &b) noexcept
{
return !(a == b);
}
private:
Storage storage_;
};
} // namespace nbt
// --- ListTag ---
template <typename T>
requires(!std::is_same_v<std::remove_cvref_t<T>, nbt::Tag>)
ListTag::ListTag(std::initializer_list<T> init)
{
for (const auto &item : init) {
value_type v(item);
const auto t = v.type();
if (t == nbt::Type::End) {
throw std::invalid_argument("ListTag cannot contain End tags.");
}
if (type_ == nbt::Type::End) {
type_ = t;
}
else if (t != type_) {
throw std::invalid_argument("ListTag elements must have the same type.");
}
elements_.emplace_back(v);
}
}
inline bool ListTag::empty() const noexcept
{
return elements_.empty();
}
inline ListTag::size_type ListTag::size() const noexcept
{
return elements_.size();
}
inline nbt::Type ListTag::type() const noexcept
{
return type_;
}
inline ListTag::value_type &ListTag::at(size_type i)
{
return elements_.at(i);
}
inline const ListTag::value_type &ListTag::at(size_type i) const
{
return elements_.at(i);
}
inline ListTag::value_type &ListTag::operator[](size_type i)
{
return elements_[i];
}
inline const ListTag::value_type &ListTag::operator[](size_type i) const
{
return elements_[i];
}
inline void ListTag::clear() noexcept
{
elements_.clear();
type_ = nbt::Type::End;
}
template <class... Args>
ListTag::value_type &ListTag::emplace_back(Args &&...args)
{
value_type v(std::forward<Args>(args)...);
const auto t = v.type();
if (t == nbt::Type::End) {
throw std::invalid_argument("ListTag cannot contain End tags.");
}
if (type_ == nbt::Type::End) {
type_ = t;
}
else if (t != type_) {
throw std::invalid_argument("ListTag elements must have the same type.");
}
elements_.emplace_back(v);
return elements_.back();
}
inline ListTag::iterator ListTag::erase(const_iterator pos)
{
return elements_.erase(pos);
}
inline ListTag::iterator ListTag::erase(const_iterator first, const_iterator last)
{
return elements_.erase(first, last);
}
inline ListTag::iterator ListTag::begin() noexcept
{
return elements_.begin();
}
inline ListTag::iterator ListTag::end() noexcept
{
return elements_.end();
}
inline ListTag::const_iterator ListTag::begin() const noexcept
{
return elements_.begin();
}
inline ListTag::const_iterator ListTag::end() const noexcept
{
return elements_.end();
}
inline ListTag::const_iterator ListTag::cbegin() const noexcept
{
return elements_.cbegin();
}
inline ListTag::const_iterator ListTag::cend() const noexcept
{
return elements_.cend();
}
inline bool operator==(const ListTag &a, const ListTag &b) noexcept
{
return a.elements_ == b.elements_;
}
inline bool operator!=(const ListTag &a, const ListTag &b) noexcept
{
return !(a == b);
}
// --- CompoundTag
inline CompoundTag::CompoundTag(std::initializer_list<std::pair<std::string, nbt::Tag>> init)
: entries_(init.begin(), init.end())
{
}
inline bool CompoundTag::empty() const noexcept
{
return entries_.empty();
}
inline CompoundTag::size_type CompoundTag::size() const noexcept
{
return entries_.size();
}
inline CompoundTag::value_type &CompoundTag::at(const key_type &key)
{
return entries_.at(key);
}
inline const CompoundTag::value_type &CompoundTag::at(key_type key) const
{
return entries_.at(key);
}
inline CompoundTag::value_type &CompoundTag::operator[](const key_type &key)
{
return entries_[key];
}
inline bool CompoundTag::contains(const key_type &key) const noexcept
{
return entries_.contains(key);
}
inline void CompoundTag::clear() noexcept
{
entries_.clear();
}
template <class P>
std::pair<CompoundTag::iterator, bool> CompoundTag::insert(P &&v)
{
return entries_.insert(std::forward<P>(v));
}
template <class... Args>
std::pair<CompoundTag::iterator, bool> CompoundTag::emplace(Args &&...args)
{
return entries_.emplace(std::forward<Args>(args)...);
}
template <class... Args>
std::pair<CompoundTag::iterator, bool> CompoundTag::try_emplace(const key_type &key, Args &&...args)
{
return entries_.try_emplace(key, std::forward<Args>(args)...);
}
template <class M>
std::pair<CompoundTag::iterator, bool> CompoundTag::insert_or_assign(const key_type &key, M &&obj)
{
return entries_.insert_or_assign(key, std::forward<M>(obj));
}
inline CompoundTag::iterator CompoundTag::erase(const_iterator pos)
{
return entries_.erase(pos);
}
inline CompoundTag::size_type CompoundTag::erase(const key_type &key)
{
return entries_.erase(key);
}
inline CompoundTag::iterator CompoundTag::erase(const_iterator first, const_iterator last)
{
return entries_.erase(first, last);
}
inline void CompoundTag::swap(CompoundTag &other) noexcept
{
entries_.swap(other.entries_);
}
inline void CompoundTag::merge(CompoundTag &source)
{
for (const auto &[k, v] : source.entries_) {
entries_.try_emplace(k, v);
}
}
inline void CompoundTag::merge(CompoundTag &&source)
{
for (const auto &[k, v] : source.entries_) {
entries_.try_emplace(k, std::move(v));
}
}
inline CompoundTag::iterator CompoundTag::begin() noexcept
{
return entries_.begin();
}
inline CompoundTag::iterator CompoundTag::end() noexcept
{
return entries_.end();
}
inline CompoundTag::const_iterator CompoundTag::begin() const noexcept
{
return entries_.begin();
}
inline CompoundTag::const_iterator CompoundTag::end() const noexcept
{
return entries_.end();
}
inline CompoundTag::const_iterator CompoundTag::cbegin() const noexcept
{
return entries_.cbegin();
}
inline CompoundTag::const_iterator CompoundTag::cend() const noexcept
{
return entries_.cend();
}
inline bool operator==(const CompoundTag &a, const CompoundTag &b) noexcept
{
return a.entries_ == b.entries_;
}
inline bool operator!=(const CompoundTag &a, const CompoundTag &b) noexcept
{
return !(a == b);
}
namespace nbt {
struct escape_view {
std::string_view value;
};
inline escape_view escaped(std::string_view value)
{
return {value};
}
} // namespace nbt
} // namespace endstone
template <>
struct fmt::formatter<endstone::nbt::Tag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::nbt::Tag &tag, FormatContext &ctx) const -> format_context::iterator
{
return tag.visit([&](auto &&arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>) {
return ctx.out();
}
else {
return fmt::format_to(ctx.out(), "{}", arg);
}
});
}
};
template <>
struct fmt::formatter<endstone::ByteTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::ByteTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "{}b", tag.value());
}
};
template <>
struct fmt::formatter<endstone::ShortTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::ShortTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "{}s", tag.value());
}
};
template <>
struct fmt::formatter<endstone::IntTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::IntTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "{}", tag.value());
}
};
template <>
struct fmt::formatter<endstone::LongTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::LongTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "{}L", tag.value());
}
};
template <>
struct fmt::formatter<endstone::FloatTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::FloatTag &tag, FormatContext &ctx) const -> format_context::iterator
{
std::string s = fmt::format("{:g}", tag.value());
if (s.find('.') == std::string::npos && s.find('e') == std::string::npos) {
s += ".0";
}
return fmt::format_to(ctx.out(), "{}f", s);
}
};
template <>
struct fmt::formatter<endstone::DoubleTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::DoubleTag &tag, FormatContext &ctx) const -> format_context::iterator
{
std::string s = fmt::format("{:g}", tag.value());
if (s.find('.') == std::string::npos && s.find('e') == std::string::npos) {
s += ".0";
}
return fmt::format_to(ctx.out(), "{}d", s);
}
};
template <>
struct fmt::formatter<endstone::ByteArrayTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::ByteArrayTag &tag, FormatContext &ctx) const -> format_context::iterator
{
auto out = ctx.out();
out = fmt::format_to(out, "[B;");
auto it = tag.begin();
out = fmt::format_to(out, "{}b", *it);
++it;
while (it != tag.end()) {
fmt::format_to(out, ",{}b", *it);
++it;
}
out = fmt::format_to(out, "]");
return out;
}
};
template <>
struct fmt::formatter<endstone::StringTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::StringTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "{}", endstone::nbt::escaped(tag.value()));
}
};
template <>
struct fmt::formatter<endstone::IntArrayTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::IntArrayTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "[I;{}]", fmt::join(tag.begin(), tag.end(), ","));
}
};
template <>
struct fmt::formatter<endstone::ListTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::ListTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "[{}]", fmt::join(tag.begin(), tag.end(), ","));
}
};
template <>
struct fmt::formatter<endstone::CompoundTag::map_type::value_type> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::CompoundTag::map_type::value_type &pair, FormatContext &ctx) const
-> format_context::iterator
{
return fmt::format_to(ctx.out(), "{}:{}", endstone::nbt::escaped(pair.first), pair.second);
}
};
template <>
struct fmt::formatter<endstone::CompoundTag> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::CompoundTag &tag, FormatContext &ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "{}", fmt::join(tag.begin(), tag.end(), ","));
}
};
template <>
struct fmt::formatter<endstone::nbt::escape_view> : formatter<string_view> {
template <typename FormatContext>
auto format(const endstone::nbt::escape_view &v, FormatContext &ctx) const -> format_context::iterator
{
static const std::regex simple_value("[A-Za-z0-9._+-]+");
if (std::regex_match(v.value.begin(), v.value.end(), simple_value)) {
return fmt::format_to(ctx.out(), "{}", v.value);
}
std::string out = " "; // placeholder for chosen quote char
char quote = 0;
for (const char c : v.value) {
switch (c) {
case '\\':
out.push_back('\\');
out.push_back('\\');
break;
case '{':
out += "; // fmt escape
break;
case '}':
out += "; // fmt escape
break;
case '"':
case '\'':
if (quote == 0) {
// choose opposite quote
quote = (c == '"') ? '\'' : '"';
}
if (c == quote) {
out.push_back('\\');
}
out.push_back(c);
break;
default:
out.push_back(c);
break;
}
}
if (quote == 0) {
quote = '"';
}
out[0] = quote;
out.push_back(quote);
return fmt::format_to(ctx.out(), "{}", out);
}
};