From 14d7e7275eb54efdfe59ba3ce3b53634286c3d05 Mon Sep 17 00:00:00 2001 From: Markus Mittendrein Date: Sat, 20 Jul 2019 23:22:45 +0200 Subject: Initial --- CMakeLists.txt | 10 ++ cxxformat.hpp | 435 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 64 +++++++++ 3 files changed, 509 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cxxformat.hpp create mode 100644 main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5f75a25 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.0) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +project(cxxformat) + +add_executable(cxxformat main.cpp) + +install(TARGETS cxxformat RUNTIME DESTINATION bin) diff --git a/cxxformat.hpp b/cxxformat.hpp new file mode 100644 index 0000000..d4e8dfd --- /dev/null +++ b/cxxformat.hpp @@ -0,0 +1,435 @@ +/* +Copyright 2019 Mittendrein Markus + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace format { + +using FormatException = std::invalid_argument; + +template +struct FormatConvert {}; + +template +struct FormatConvert && std::is_integral_v>> +{ + static constexpr std::intmax_t convert(From from) + { + return from; + } +}; + +template +struct FormatConvert && std::is_integral_v && (sizeof(From) < sizeof(std::intmax_t))>> +{ + static constexpr std::intmax_t convert(From from) + { + return from; + } +}; + +template +struct FormatConvert && std::is_integral_v>> +{ + static constexpr std::uintmax_t convert(From from) + { + return from; + } +}; + +template +struct FormatConvert>> +{ + static constexpr std::string_view convert(const String &str) + { + return str; + } +}; + +template +struct FormatConvert && !std::is_integral_v>> +{ + static constexpr double convert(const From& val) + { + return val; + } +}; + +template +struct FormatConvert && !std::is_array_v> && !std::is_same_v, char>>> +{ + static constexpr const void *convert(const Pointer* pointer) + { + return pointer; + } +}; + +template +struct FormatConvert && std::is_array_v>>> +{ + static constexpr const void *convert(const Pointer pointer) + { + return pointer; + } +}; + +template +struct AutoConversion +{ + static std::string conversion(const From &) + { + throw FormatException{""}; + } +}; + +template +struct AutoConversion && std::is_unsigned_v>> +{ + static std::string conversion(const From &) + { + return "u"; + } +}; + +template +struct AutoConversion && std::is_signed_v>> +{ + static std::string conversion(const From &) + { + return "d"; + } +}; + +template +struct AutoConversion && !std::is_integral_v>> +{ + static std::string conversion(const From &) + { + return "G"; + } +}; + +template +struct AutoConversion>> +{ + static std::string conversion(const From &) + { + return "s"; + } +}; + +template<> +struct AutoConversion +{ + static std::string conversion(const char) + { + return "c"; + } +}; + +namespace detail +{ + template + struct remove_cvref { using type = std::remove_cv_t>; }; + + template + using remove_cvref_t = typename remove_cvref::type; + + template + struct StointHelper + { + static T convert(const std::string &, std::size_t *, int) + { + static_assert(Ok, "stoint: Unsupported type"); + return T{}; + } + }; + + template + struct StointHelperConverter + { + static T convert(const std::string &str, std::size_t *pos, int base) + { + return converter(str, pos, base); + } + }; + + template<> + struct StointHelper : StointHelperConverter {}; + template<> + struct StointHelper : StointHelperConverter {}; + template<> + struct StointHelper : StointHelperConverter {}; + template<> + struct StointHelper : StointHelperConverter {}; + template<> + struct StointHelper : StointHelperConverter {}; + + template + T stoint(const std::string &str, std::size_t *pos = nullptr, int base = 10) + { + return StointHelper::convert(str, pos, base); + } + + template + std::string strprintf(const char *fmt, Args... args) + { + int size = std::snprintf(nullptr, 0, fmt, args...) + 1; + std::string result; + result.resize(size); + std::snprintf(result.data(), size, fmt, args...); + result.resize(size - 1); + return result; + } + + template + struct FormatConvertHelper + { + static constexpr To convert(const From &) + { + static_assert(allowFallback, "Impossible conversion needed"); + throw FormatException{""}; + } + }; + + template + struct FormatConvertHelper + { + static constexpr const T &convert(const T &from) + { + return from; + } + }; + + template + struct FormatConvertHelper && std::is_same_v>::convert(std::declval()))>, remove_cvref_t>>> + { + static constexpr To convert(const From &from) + { + return FormatConvert>::convert(from); + } + }; + + template + auto convert(const From &from, const char *toName, std::size_t index = 0) + { + try + { + return FormatConvertHelper::convert(from); + } + catch (const FormatException &) + { + throw FormatException{"Argument#" + std::to_string(index) + " is not convertible to " + std::string{toName}}; + } + } + + template + struct StringOrStringViewHelper + { + using type = std::string_view; + }; + + template + struct StringOrStringViewHelper::convert(std::declval()))>>>> + { + using type = std::string; + }; + + template + using string_or_string_view = typename StringOrStringViewHelper::type; + + template + std::string autoConversionSpecifier(From &&from, std::size_t index = 0) + { + try + { + return AutoConversion>::conversion(std::forward(from)); + } + catch (const FormatException &) + { + if constexpr (std::is_pointer_v) + { + if constexpr (allowPointer) + { + return "p"; + } + else + { + throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion with %v, use %V to also allow arbitrary pointers"}; + } + } + throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion (%v / %V)"}; + } + } + + std::string format(std::string_view fmt, const std::size_t argumentIndex) + { + const auto fmtSize = fmt.size(); + for (std::size_t i = 0; i < fmtSize; ++i) + { + if (fmt[i] == '%') + { + if (i >= fmtSize - 1) + { + throw FormatException{"Incomplete directive"}; + } + + if (fmt[++i] == '%') + { + return std::string{fmt.substr(0, i)} + format(fmt.substr(i + 1), argumentIndex); + } + + throw FormatException{"Not enough arguments passed"}; + } + } + return std::string{fmt}; + } + + template + std::string format(std::string_view fmt, const std::size_t argumentIndex, Arg &&arg, Args &&... args) + { + const auto fmtSize = fmt.size(); + for (std::size_t i = 0; i < fmtSize; ++i) + { + auto checkFmtLength = [i, fmtSize] + { + if (i >= fmtSize - 1) + { + throw FormatException{"Incomplete directive"}; + } + }; + const auto current = fmt[i]; + if (current == '%') + { + const auto directiveStart = i; + checkFmtLength(); + std::string specification{"%"}; + + const auto flag = fmt[++i]; + if (flag == '%') // handle %% here + { + return std::string{fmt.substr(0, directiveStart + 1)} + format(fmt.substr(i + 1), argumentIndex, std::forward(arg), std::forward(args)...); + } + switch (flag) + { + case '#': case '0': case '-': case ' ': case '+': + specification.push_back(flag); + checkFmtLength(); + break; + default: + --i; + } + + std::string tempNumber; + // field width + while (std::isdigit(fmt[++i])) + { + tempNumber.push_back(fmt[i]); + checkFmtLength(); + } + --i; + const auto fieldWidth = tempNumber.empty() ? 0 : stoint(tempNumber); + tempNumber.clear(); + + std::optional precision; + // precision + if (fmt[++i] == '.') + { + checkFmtLength(); + while (std::isdigit(fmt[++i])) + { + tempNumber.push_back(fmt[i]); + checkFmtLength(); + } + precision = tempNumber.empty() ? 0 : stoint(tempNumber); + } + --i; + + std::string conversion{fmt[++i]}; + if(conversion.front() == 'v') + { + conversion = autoConversionSpecifier(std::forward(arg), argumentIndex); + } + else if(conversion.front() == 'V') + { + conversion = autoConversionSpecifier(std::forward(arg), argumentIndex); + } + + auto continueFormatting = [directiveStart, &args..., fmt, &specification, &fieldWidth, &precision, i, conversion, argumentIndex](auto length, auto converted) + { + if (fieldWidth) specification += std::to_string(fieldWidth); + if (precision) specification += '.' + std::to_string(*precision); + if (length) specification += length; + specification += conversion; + return std::string{fmt.substr(0, directiveStart)} + strprintf(specification.c_str(), converted) + format(fmt.substr(i + 1), argumentIndex + 1, std::forward(args)...); + }; + + switch (conversion.back()) + { + case 'c': + return continueFormatting(false, convert(std::forward(arg), "character", argumentIndex)); + + case 'd': case 'i': + return continueFormatting('j', convert(std::forward(arg), "signed integer", argumentIndex)); + + case 'u': case 'o': case 'x': case 'X': + return continueFormatting('j', convert(std::forward(arg), "unsigned integer", argumentIndex)); + + case 's': + { + const auto& string = convert>>(std::forward(arg), "string", argumentIndex); + precision = precision ? std::min(*precision, string.size()) : string.size(); + return continueFormatting(false, string.data()); + } + + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A': + return continueFormatting(false, convert(std::forward(arg), "double", argumentIndex)); + + case 'p': + return continueFormatting(false, convert(std::forward(arg), "pointer", argumentIndex)); + + default: + throw FormatException{std::string{"Unknown conversion specifier: \""} + conversion + "\""}; + } + } + } + + throw FormatException{std::to_string(argumentIndex - 1) + " placeholders found, but " + std::to_string(argumentIndex + sizeof...(args)) + " arguments passed"}; + } +} + +template +std::string format(const String &fmt, Args &&... args) +{ + const auto fmtStr = detail::convert(fmt, "string"); + try + { + return detail::format(fmtStr, 1, std::forward(args)...); + } + catch (const FormatException &e) + { + const auto &message = std::string{"format(\""} + std::string{fmtStr} + "\", " + std::to_string(sizeof...(args)) + " more arguments...): " + e.what(); + + if constexpr (NoThrow) return message; + else throw FormatException(message); + } +} + +} \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..7a0ccd0 --- /dev/null +++ b/main.cpp @@ -0,0 +1,64 @@ +#include "cxxformat.hpp" +#include +#include + + +template +struct format::FormatConvert, char>>> +{ + template + static std::string accessConvert(const T *from) + { + if constexpr (_N == N) + { + return "}"; + } + else + { + return format::format("%v", from[_N]) + (_N + 1 < N ? ", " : "") + accessConvert<_N + 1>(from); + } + } + + static std::string convert(const T *from) + { + return "array[" + std::to_string(N) + "]{" + accessConvert<0>(from); + } +}; + +template +struct format::FormatConvert> +{ + static std::string convert(const std::array& from) + { + return format::FormatConvert::convert(from.data()); + } +}; + +template +struct format::AutoConversion> +{ + static std::string conversion(const std::array&) + { + return "s"; + } +}; + +template +struct format::AutoConversion, char>>> +{ + static std::string conversion(const T(&)[N]) + { + return "s"; + } +}; + +int main(int argc, char *argv[]) +{ + std::cout << format::format("%v%v%v%v%v", 'H', 'a', 'l', 'l', 'o') << std::endl; + const std::array array[] = {std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}}; + std::cout << format::format("%V", &array) << std::endl; + std::cout << format::format("%s", std::array{1, 2, 3, 4, 5}) << std::endl; + std::cout << format::format("Hello %v: %-7v %v", "World", 3.5, 5) << std::endl; + std::cout << format::format("Hello %s: %d %03.5f", "World", 7, 3.5) << std::endl; + return 0; +} -- cgit v1.2.3-54-g00ecf