TalTech_cpp/CT4/menugen.cpp

143 lines
6.5 KiB
C++

#ifndef MENUGEN_0219820379823487123785
#define MENUGEN_0219820379823487123785
#if __cplusplus < 202002L
#error This code requires C++20
#endif
// This is collection of utility constexpr tools for generating menu dynamically in compile time
// Menu::generateMenuMessage basically creates this:
// constexpr const char* MENU_MSG = "\n\n=== Ticket Booking System ===\n1. Configure\n2. Reserve\n3. Cancel\n4. Check\n5. Exit\n";
// enum class State : int {
// MENU = 0,
// SETUP = 1,
// RESERVE = 2,
// CANCEL = 3,
// CHECK = 4,
// EXIT = 5,
// };
// Its kinda long & ugly because we need to generate both symbols in enum and use their values in the message
// Menu entries are defined with generic macro X() which is defined where menu is expanded and formats each entry there.
// This is trivial for pure enum creation, but we also need to count them, this is achived with Menu::State::COUNT beeing last element
// Next step is creating message. To be constexpr we cant use string, I experimented with string_view for few hours,
// but they are a lot less versatile than std::array, so Ive got first prototype working with them.
// But STL containers are forbidden in CT2... so i wrote my own ConstString :)
// With constexpr ready container, generating message is simple as
// 1. transforming number(enum member) to string
// 2. summing up all the submessage lengths
// 3. copying to single container
// I was suprised cpp20 doesnt include way to transform int to string in constexpr.
// So i achived this with recursive template ExpandDecimal - name is self explanatory.
// Calculating total length is just sum of the sizes of expanded template pack
// And copying with std::array would be just std::copy_n, but same can be achived on my structure with index_sequence from utility library
// thats all, hope you find it interesting :)
#include <utility> // index_sequence
namespace Menu{
constexpr auto RESET = "\033[0m";
constexpr auto RED = "\033[31m";
constexpr auto GREEN = "\033[32m";
constexpr auto YELLOW = "\033[33m";
constexpr auto BOLDWHITE = "\033[1m\033[37m";
constexpr auto CURSOR = "> ";
using StateType = size_t;
//###################################################################################
// helper char[] wrapper since it cannot be directly returned
template<size_t size>
struct ConstString { char data[size + 1];};
template<size_t size>
std::ostream& operator<<(std::ostream& os, const ConstString<size>& r) {
for (char c : r.data)
os << c;
return os;
}
//###################################################################################
// helpers to convert int (from enum) to string in ##################################
// shared array to store result
template<StateType... digits>
struct to_chars { static const char value[]; };
// convert single digit to char
template<StateType... digits>
// constexpr char to_chars<digits...>::value[] = {('0' + digits)...};
constexpr char to_chars<digits...>::value[] = {('0' + digits)..., 0}; // null termination
// when reminder is not zero - recursive expand for each decimal digit
template<StateType rem, StateType... digits>
struct ExpandDecimal : ExpandDecimal<rem / 10, rem % 10, digits...> {};
// when reminder is not zero - call to_char for last digit
template<StateType... digits>
struct ExpandDecimal<0, digits...> : to_chars<digits...> {};
// entry point - to by called directly
template<StateType num>
struct NumToString : ExpandDecimal<num> {};
//###################################################################################
// helper templates to calculate total length as constexpr ##########################
template<typename... Strings>
constexpr std::size_t total_length(Strings&&... strings) {
return (0 + ... + std::size(strings));
}
//###################################################################################
// helper templates to concatonate as constexpr #####################################
template<typename Str, std::size_t... I> // analog to copy_n from <algorithm>
static constexpr void copy_str(char* dest, std::size_t& idx,
Str str, std::index_sequence<I...>) {
((dest[idx++] = str[I]), ...);
}
template<typename... Strings>
constexpr auto concat(Strings&&... strings) {
constexpr std::size_t total_len = (0 + ... + (std::size(strings) - 1));
ConstString<total_len> result; std::size_t idx = 0;
((copy_str(result.data, idx, strings, // copy each string to result
std::make_index_sequence<std::size(strings) - 1>{}), ...));
result.data[total_len] = '\0';
return result;
}
//###################################################################################
// tests ############################################################################
static_assert(sizeof(NumToString<1>::value) == 2, "NumToString: size check");
static_assert(NumToString<1>::value[0] == '1', "NumToString: 1");
static_assert(NumToString<12345678900>::value[0] == '1' &&
NumToString<12345678900>::value[1] == '2' &&
NumToString<12345678900>::value[2] == '3' &&
NumToString<12345678900>::value[3] == '4' &&
NumToString<12345678900>::value[4] == '5' &&
NumToString<12345678900>::value[5] == '6' &&
NumToString<12345678900>::value[6] == '7' &&
NumToString<12345678900>::value[7] == '8' &&
NumToString<12345678900>::value[8] == '9' &&
NumToString<12345678900>::value[9] == '0' &&
NumToString<12345678900>::value[10] == '0' &&
NumToString<12345678900>::value[11] == '\0'&&
sizeof(NumToString<12345678900>::value) == 12, "NumToString: longnum");
static_assert(sizeof(concat("a", "bc", "def")) == sizeof("abcdef") , "concat: size check");
static_assert(concat("a", "bc", "def").data[0] == 'a' &&
concat("a", "bc", "def").data[1] == 'b' &&
concat("a", "bc", "def").data[2] == 'c' &&
concat("a", "bc", "def").data[3] == 'd' &&
concat("a", "bc", "def").data[4] == 'e' &&
concat("a", "bc", "def").data[5] == 'f' &&
concat("a", "bc", "def").data[6] == '\0', "concat: content");
}
#endif //MENUGEN_0219820379823487123785