478 lines
14 KiB
C++
478 lines
14 KiB
C++
//
|
|
// tagfile.hpp
|
|
// BoE
|
|
//
|
|
// Created by Celtic Minstrel on 2022-07-12.
|
|
//
|
|
|
|
#ifndef BoE_tagfile_hpp
|
|
#define BoE_tagfile_hpp
|
|
|
|
#include <string>
|
|
#include <deque>
|
|
#include <map>
|
|
#include <functional> // for reference_wrapper
|
|
#include <istream>
|
|
#include <ostream>
|
|
#include <memory>
|
|
#include <boost/optional.hpp>
|
|
#include "tools/vector2d.hpp"
|
|
|
|
namespace detail {
|
|
// To allow ADL with custom begin/end
|
|
using std::begin;
|
|
using std::end;
|
|
|
|
template <typename T>
|
|
auto is_iterable_impl(int)
|
|
-> decltype (
|
|
begin(std::declval<T&>()) != end(std::declval<T&>()), // begin/end and operator !=
|
|
void(), // Handle evil operator ,
|
|
++std::declval<decltype(begin(std::declval<T&>()))&>(), // operator ++
|
|
void(*begin(std::declval<T&>())), // operator*
|
|
std::true_type{}
|
|
);
|
|
|
|
template <typename T>
|
|
std::false_type is_iterable_impl(...);
|
|
|
|
template<typename T>
|
|
struct is_container : decltype(detail::is_iterable_impl<T>(0)) {};
|
|
|
|
template<typename T, size_t n>
|
|
struct is_container<T[n]> : std::false_type {};
|
|
|
|
template<>
|
|
struct is_container<std::string> : std::false_type {};
|
|
|
|
template<typename T>
|
|
struct is_pair : public std::false_type {};
|
|
|
|
template<typename A, typename B>
|
|
struct is_pair<std::pair<A, B>> : public std::true_type {};
|
|
}
|
|
|
|
class cTagFile;
|
|
class cTagFile_Page;
|
|
|
|
class cTagFile_Tag {
|
|
std::deque<std::string> values;
|
|
public:
|
|
// All extract/encode functions take a starting index and
|
|
// return the number of values extracted/encoded (usually 1).
|
|
size_t extract(size_t i, cTagFile_Tag&) const = delete;
|
|
size_t encode(size_t i, const cTagFile_Tag&) = delete;
|
|
size_t extract(size_t i, std::string& to) const;
|
|
size_t encode(size_t i, const std::string& from);
|
|
size_t extract(size_t i, bool& to) const;
|
|
size_t encode(size_t i, const bool& from);
|
|
size_t extract(size_t i, char& to) const;
|
|
size_t encode(size_t i, const char& from);
|
|
size_t extract(size_t i, signed char& to) const;
|
|
size_t encode(size_t i, const signed char& from);
|
|
size_t extract(size_t i, unsigned char& to) const;
|
|
size_t encode(size_t i, const unsigned char& from);
|
|
size_t extract(size_t i, std::tuple<>& to) const;
|
|
size_t encode(size_t i, const std::tuple<>& from);
|
|
|
|
template<typename T>
|
|
typename std::enable_if<!detail::is_container<T>::value, size_t>::type
|
|
extract(size_t i, T& to) const {
|
|
if(i >= values.size()) return 0;
|
|
// This bizarre construct simply allows us to not include <sstream> here
|
|
using stream = typename std::conditional<std::is_same<T, T>::value, std::istringstream, void>::type;
|
|
stream file(values[i]);
|
|
file >> to;
|
|
return 1;
|
|
}
|
|
|
|
template<typename T>
|
|
typename std::enable_if<!detail::is_container<T>::value, size_t>::type
|
|
encode(size_t i, const T& from) {
|
|
if(i >= values.size()) values.resize(i + 1);
|
|
// This bizarre construct simply allows us to not include <sstream> here
|
|
using stream = typename std::conditional<std::is_same<T, T>::value, std::ostringstream, void>::type;
|
|
stream file;
|
|
file << from;
|
|
values[i] = file.str();
|
|
return 1;
|
|
}
|
|
|
|
template<typename A, typename B>
|
|
size_t extract(size_t i, std::pair<A, B>& to) const {
|
|
return extract(i, to.first) + extract(i + 1, to.second);
|
|
}
|
|
|
|
template<typename A, typename B>
|
|
size_t encode(size_t i, const std::pair<A, B>& from) {
|
|
return encode(i, from.first) + encode(i + 1, from.second);
|
|
}
|
|
|
|
private:
|
|
template<typename... T>
|
|
struct handle_tuple {
|
|
template<size_t j = 0>
|
|
typename std::enable_if<j < sizeof...(T), size_t>::type
|
|
extract(const cTagFile_Tag& self, size_t i, std::tuple<T...>& to) {
|
|
if(j >= sizeof...(T)) return 0;
|
|
return self.extract(i, std::get<j>(to)) + extract<j + 1>(self, i + 1, to);
|
|
}
|
|
template<size_t j = 0>
|
|
typename std::enable_if<j >= sizeof...(T), size_t>::type
|
|
extract(const cTagFile_Tag&, size_t, std::tuple<T...>&) {
|
|
return 0;
|
|
}
|
|
template<size_t j = 0>
|
|
typename std::enable_if<j < sizeof...(T), size_t>::type
|
|
encode(cTagFile_Tag& self, size_t i, const std::tuple<T...>& from) {
|
|
if(j >= sizeof...(T)) return 0;
|
|
return self.encode(i, std::get<j>(from)) + encode<j + 1>(self, i + 1, from);
|
|
}
|
|
template<size_t j = 0>
|
|
typename std::enable_if<j >= sizeof...(T), size_t>::type
|
|
encode(cTagFile_Tag&, size_t, const std::tuple<T...>&) {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
public:
|
|
template<typename... T>
|
|
size_t extract(size_t i, std::tuple<T...>& to) const {
|
|
handle_tuple<T...> handle;
|
|
return handle.extract(*this, i, to);
|
|
}
|
|
|
|
template<typename... T>
|
|
size_t encode(size_t i, const std::tuple<T...>& from) {
|
|
handle_tuple<T...> handle;
|
|
return handle.encode(*this, i, from);
|
|
}
|
|
|
|
template<typename T, size_t n>
|
|
size_t extract(size_t i, std::array<T, n>& to) const {
|
|
for(size_t j = 0; j < n; j++) {
|
|
extract(i + j, to[j]);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
template<typename T, size_t n>
|
|
size_t encode(size_t i, const std::array<T, n>& from) {
|
|
for(size_t j = 0; j < n; j++) {
|
|
encode(i + j, from[j]);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
template<typename T>
|
|
size_t extract(size_t i, boost::optional<T>& to) const {
|
|
if(i < values.size()) {
|
|
return extract(i, *to);
|
|
} else {
|
|
to = boost::none;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
size_t encode(size_t i, const boost::optional<T>& from) {
|
|
return from ? encode(i, *from) : 0;
|
|
}
|
|
|
|
template<typename Container>
|
|
typename std::enable_if<detail::is_container<Container>::value, size_t>::type
|
|
extract(size_t i, Container& to) const {
|
|
using T = typename Container::value_type;
|
|
to.clear();
|
|
for(size_t n = i; n < values.size(); n++) {
|
|
T value;
|
|
extract(n, value);
|
|
to.push_back(value);
|
|
}
|
|
return to.size();
|
|
}
|
|
|
|
template<typename Container>
|
|
typename std::enable_if<detail::is_container<Container>::value, size_t>::type
|
|
encode(size_t i, const Container& from) {
|
|
for(const auto& value : from) {
|
|
encode(i++, value);
|
|
}
|
|
return from.size();
|
|
}
|
|
|
|
const std::string key;
|
|
cTagFile_Tag(const std::string& key);
|
|
void readFrom(std::istream& file);
|
|
void writeTo(std::ostream& file);
|
|
template<typename T>
|
|
bool operator==(const T& other) {
|
|
T self;
|
|
if(extract(0, self) == 0) return false;
|
|
return self == other;
|
|
}
|
|
|
|
};
|
|
|
|
class cTagFile_TagExtractProxy {
|
|
const cTagFile_Tag* tag;
|
|
size_t i = 0;
|
|
cTagFile_TagExtractProxy(const cTagFile_Tag& tag, size_t i) : tag(&tag), i(i) {}
|
|
public:
|
|
cTagFile_TagExtractProxy() : tag(nullptr) {}
|
|
cTagFile_TagExtractProxy(const cTagFile_Tag& tag) : tag(&tag) {}
|
|
explicit operator bool() const {
|
|
return tag;
|
|
}
|
|
template<typename T>
|
|
cTagFile_TagExtractProxy operator>>(T& value) {
|
|
if(tag == nullptr) return *this;
|
|
size_t j = i;
|
|
j += tag->extract(i, value);
|
|
if(i == j) return cTagFile_TagExtractProxy();
|
|
return cTagFile_TagExtractProxy(*tag, j);
|
|
}
|
|
template<typename T>
|
|
cTagFile_TagExtractProxy operator>>(const T& value) {
|
|
if(tag == nullptr) return *this;
|
|
size_t j = i;
|
|
T check = value;
|
|
j += tag->extract(i, check);
|
|
if(check != value) {
|
|
return cTagFile_TagExtractProxy();
|
|
}
|
|
return cTagFile_TagExtractProxy(*tag, j);
|
|
}
|
|
};
|
|
|
|
class cTagFile_TagEncodeProxy {
|
|
cTagFile_Tag* tag;
|
|
size_t i = 0;
|
|
cTagFile_TagEncodeProxy(cTagFile_Tag& tag, size_t i) : tag(&tag), i(i) {}
|
|
public:
|
|
cTagFile_TagEncodeProxy(cTagFile_Tag& tag) : tag(&tag) {}
|
|
template<typename T>
|
|
cTagFile_TagEncodeProxy operator<<(const T& value) {
|
|
if(tag == nullptr) return *this;
|
|
size_t j = i;
|
|
j += tag->encode(i, value);
|
|
return cTagFile_TagEncodeProxy(*tag, j);
|
|
}
|
|
};
|
|
|
|
class cTagFile_TagList {
|
|
friend class cTagFile_Page;
|
|
cTagFile_Page* parent = nullptr;
|
|
std::deque<cTagFile_Tag> tags;
|
|
mutable size_t i = 0;
|
|
cTagFile_TagList(const cTagFile_TagList&) = delete;
|
|
void internal_add_tag();
|
|
cTagFile_TagList() = default;
|
|
public:
|
|
const std::string key;
|
|
cTagFile_TagList(cTagFile_Page& parent, const std::string& key);
|
|
cTagFile_Tag& add();
|
|
cTagFile_Tag& operator[](size_t n);
|
|
const cTagFile_Tag& operator[](size_t n) const;
|
|
bool empty() const;
|
|
size_t size() const;
|
|
template<typename T>
|
|
cTagFile_TagEncodeProxy operator<<(const T& value) {
|
|
internal_add_tag();
|
|
return cTagFile_TagEncodeProxy(tags.back()) << value;
|
|
}
|
|
template<typename T>
|
|
cTagFile_TagExtractProxy operator>>(T&& value) const {
|
|
if(i >= tags.size()) {
|
|
i = 0;
|
|
return cTagFile_TagExtractProxy() >> std::forward<T>(value);
|
|
} else {
|
|
return cTagFile_TagExtractProxy(tags[i++]) >> std::forward<T>(value);
|
|
}
|
|
}
|
|
template<typename Container>
|
|
void extract(Container& values) const {
|
|
using T = typename Container::value_type;
|
|
values.clear();
|
|
i = 0;
|
|
for(size_t n = 0; n < tags.size(); n++) {
|
|
T value;
|
|
*this >> value;
|
|
values.push_back(value);
|
|
}
|
|
}
|
|
template<typename Container>
|
|
void encode(const Container& values) {
|
|
for(const auto& value : values) {
|
|
*this << value;
|
|
}
|
|
}
|
|
template<typename Container>
|
|
typename std::enable_if<!detail::is_pair<typename Container::value_type>::value>::type
|
|
extractSparse(Container& values, typename Container::value_type def = typename Container::value_type()) const {
|
|
using T = typename Container::value_type;
|
|
values.clear();
|
|
i = 0;
|
|
for(size_t n = 0; n < tags.size(); n++) {
|
|
size_t i = 0;
|
|
T value;
|
|
if(*this >> i >> value) {
|
|
if(i >= values.size()) values.resize(i + 1, def);
|
|
values[i] = value;
|
|
}
|
|
}
|
|
}
|
|
template<typename Container>
|
|
typename std::enable_if<!detail::is_pair<typename Container::value_type>::value>::type
|
|
encodeSparse(const Container& values, typename Container::value_type def = typename Container::value_type()) {
|
|
size_t i = 0;
|
|
for(const auto& value : values) {
|
|
if(value != def) {
|
|
*this << i << value;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
template<typename Container>
|
|
typename std::enable_if<detail::is_pair<typename Container::value_type>::value>::type
|
|
extractSparse(Container& values) const {
|
|
using T = std::pair<
|
|
typename std::remove_cv<typename Container::value_type::first_type>::type,
|
|
typename Container::value_type::second_type
|
|
>;
|
|
values.clear();
|
|
i = 0;
|
|
for(size_t n = 0; n < tags.size(); n++) {
|
|
T value;
|
|
*this >> value.first >> value.second;
|
|
values.insert(value);
|
|
}
|
|
}
|
|
template<typename Container>
|
|
typename std::enable_if<detail::is_pair<typename Container::value_type>::value>::type
|
|
encodeSparse(const Container& values, typename Container::value_type::second_type def = typename Container::value_type::second_type()) {
|
|
for(const auto& value : values) {
|
|
if(value.second != def) {
|
|
*this << value.first << value.second;
|
|
}
|
|
}
|
|
}
|
|
template<typename T>
|
|
void extract(vector2d<T>& values) const {
|
|
values.resize(0, tags.size());
|
|
i = 0;
|
|
for(size_t n = 0; n < tags.size(); n++) {
|
|
std::vector<T> row;
|
|
tags[n].extract(0, row);
|
|
if(row.size() >= values.width()) {
|
|
values.resize(row.size(), tags.size());
|
|
}
|
|
values.row(n) = row;
|
|
}
|
|
}
|
|
template<typename T>
|
|
void encode(const vector2d<T>& values) {
|
|
for(size_t row = 0; row < values.height(); row++) {
|
|
std::vector<T> row_contents;
|
|
row_contents.reserve(values.width());
|
|
for(size_t col = 0; col < values.width(); col++) {
|
|
row_contents.push_back(values.row(row)[col]);
|
|
}
|
|
internal_add_tag();
|
|
tags.back().encode(0, row_contents);
|
|
}
|
|
}
|
|
template<typename T>
|
|
void extractSparse(vector2d<T>& values, T def = T()) const {
|
|
values.resize(0, 0);
|
|
i = 0;
|
|
for(size_t n = 0; n < tags.size(); n++) {
|
|
size_t x = 0, y = 0;
|
|
T value;
|
|
if(*this >> x >> y >> value) {
|
|
if(x >= values.width() || y >= values.height()) {
|
|
values.resize(std::max(x + 1, values.width()), std::max(y + 1, values.height()), def);
|
|
}
|
|
values[x][y] = value;
|
|
}
|
|
}
|
|
}
|
|
template<typename T>
|
|
void encodeSparse(const vector2d<T>& values, T def = T()) {
|
|
for(size_t x = 0; x < values.width(); x++) {
|
|
for(size_t y = 0; y < values.height(); y++) {
|
|
const auto& value = values[x][y];
|
|
if(value != def) {
|
|
*this << x << y << value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class cTagFile_Page {
|
|
friend class cTagFile_TagList;
|
|
// Tags are kept in a map by key, and there can be more than one of each tag.
|
|
std::map<std::string, cTagFile_TagList> tag_map;
|
|
// However, we also record the real order the tags were encountered in.
|
|
std::deque<std::reference_wrapper<cTagFile_Tag>> tag_list;
|
|
cTagFile_Page(const cTagFile_Page&) = delete;
|
|
void internal_add_page(const std::string& key);
|
|
const size_t i;
|
|
public:
|
|
std::string getFirstKey() const;
|
|
cTagFile_Page(size_t index);
|
|
void readFrom(std::istream& file);
|
|
void writeTo(std::ostream& file) const;
|
|
void clear();
|
|
cTagFile_Tag& add(const std::string& key);
|
|
bool contains(const std::string& key) const;
|
|
size_t index() const;
|
|
cTagFile_TagList& operator[](const std::string& key);
|
|
const cTagFile_TagList& operator[](const std::string& key) const;
|
|
};
|
|
|
|
class cTagFile {
|
|
std::deque<cTagFile_Page> pages;
|
|
using iterator = typename std::deque<cTagFile_Page>::iterator;
|
|
using const_iterator = typename std::deque<cTagFile_Page>::const_iterator;
|
|
public:
|
|
void readFrom(std::istream& file);
|
|
void writeTo(std::ostream& file) const;
|
|
void clear();
|
|
cTagFile_Page& add();
|
|
cTagFile_Page& operator[](size_t n);
|
|
const cTagFile_Page& operator[](size_t n) const;
|
|
iterator begin() { return pages.begin(); }
|
|
iterator end() { return pages.end(); }
|
|
const_iterator begin() const { return pages.begin(); }
|
|
const_iterator end() const { return pages.end(); }
|
|
|
|
};
|
|
|
|
template<typename Int, typename = typename std::enable_if<std::is_integral<Int>::value>::type>
|
|
struct as_hex {
|
|
Int value;
|
|
as_hex(Int value = Int()) : value(value) {}
|
|
as_hex& operator= (Int newval) { value = newval; return *this; }
|
|
operator Int() { return value; }
|
|
};
|
|
|
|
template<typename Int>
|
|
inline std::ostream& operator<<(std::ostream& os, const as_hex<Int>& value) {
|
|
auto f = os.flags();
|
|
os << std::hex << value.value;
|
|
os.flags(f);
|
|
return os;
|
|
}
|
|
|
|
template<typename Int>
|
|
inline std::istream& operator>>(std::istream& is, as_hex<Int>& ref) {
|
|
auto f = is.flags();
|
|
is >> std::hex >> ref.value;
|
|
is.flags(f);
|
|
return is;
|
|
}
|
|
|
|
#endif
|