diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml index 466e8171..08180a9e 100644 --- a/.github/workflows/msvc.yml +++ b/.github/workflows/msvc.yml @@ -22,7 +22,7 @@ jobs: - name: Build run: | - cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=20 ./ + cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=17 ./ MSBuild.exe -version MSBuild.exe .\etl.sln diff --git a/include/etl/file_error_numbers.h b/include/etl/file_error_numbers.h index d6733308..b3e08814 100644 --- a/include/etl/file_error_numbers.h +++ b/include/etl/file_error_numbers.h @@ -109,4 +109,5 @@ SOFTWARE. #define ETL_ALGORITHM_FILE_ID "76" #define ETL_NOT_NULL_FILE_ID "77" #define ETL_SIGNAL_FILE_ID "78" +#define ETL_FORMAT_FILE_ID "79" #endif diff --git a/include/etl/format.h b/include/etl/format.h new file mode 100644 index 00000000..e65df98b --- /dev/null +++ b/include/etl/format.h @@ -0,0 +1,2244 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 BMW AG + +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. +******************************************************************************/ + +#ifndef ETL_FORMAT_INCLUDED +#define ETL_FORMAT_INCLUDED + +#include "platform.h" + +#include "algorithm.h" +#include "array.h" +#include "array_view.h" +#include "error_handler.h" +#include "limits.h" +#include "math.h" +#include "optional.h" +#include "span.h" +#include "string.h" +#include "string_view.h" +#include "type_traits.h" +#include "utility.h" +#include "variant.h" +#include "visitor.h" + +#include + +#if ETL_USING_CPP11 + +namespace etl +{ + class format_exception : public etl::exception + { + public: + + format_exception(string_type reason_, string_type file_name_, numeric_type line_number_) + : exception(reason_, file_name_, line_number_) + { + } + }; + + class bad_format_string_exception : public etl::format_exception + { + public: + + bad_format_string_exception(string_type file_name_, numeric_type line_number_) + : etl::format_exception(ETL_ERROR_TEXT("format:bad", ETL_FORMAT_FILE_ID"A"), file_name_, line_number_) + { + } + }; + + template + ETL_CONSTEXPR14 bool check_f(const char* fmt) + { + // to be implemented later + //return fmt[0] == 0; // actual check + + (void)fmt; + return true; + } + + inline void please_note_this_is_error_message_1() noexcept {} + + template + struct basic_format_string + { + inline ETL_CONSTEVAL basic_format_string(const char* fmt) + : _sv(fmt) + { + bool format_string_ok = check_f(fmt); + + if (!format_string_ok) + { + //if (etl::is_constant_evaluated()) // compile time error path + //{ + // // calling a non-constexpr function in a consteval context to trigger a compile error + // please_note_this_is_error_message_1(); + //} + //else // run time error path + //{ + ETL_ASSERT_FAIL_AND_RETURN(ETL_ERROR(bad_format_string_exception)); + //} + } + } + + ETL_CONSTEXPR basic_format_string(const basic_format_string& other) = default; + ETL_CONSTEXPR14 basic_format_string& operator=(const basic_format_string& other) = default; + + ETL_CONSTEXPR string_view get() const + { + return _sv; + } + + private: + string_view _sv; + }; + + template + using format_string = basic_format_string...>; + + // supported types to format + using supported_format_types = etl::variant< + etl::monostate, + bool, + char, + int, + unsigned int, + long long int, + unsigned long long int, + float, + double, + long double, + const char*, + etl::string_view, + const void* + // basic_format_arg::handle, + >; + + template + class basic_format_parse_context + { + public: + + using iterator = string_view::const_iterator; + using const_iterator = string_view::const_iterator; + using char_type = CharT; + + basic_format_parse_context(etl::string_view fmt, size_t n_args = 0) + : range(fmt), num_args(n_args), current(0), automatic_mode(false), manual_mode(false) + { + } + + basic_format_parse_context& operator=(const basic_format_parse_context&) = delete; + + iterator begin() const noexcept + { + return range.begin(); + } + + iterator end() const noexcept + { + return range.end(); + } + + ETL_CONSTEXPR14 void advance_to(iterator pos) + { + range = etl::string_view(pos, range.end()); + } + + ETL_CONSTEXPR14 size_t next_arg_id() + { + // automatic number generation only allowed if not already in manual mode + ETL_ASSERT(manual_mode == false, ETL_ERROR(bad_format_string_exception)); + automatic_mode = true; + // TODO: compile time check + ETL_ASSERT(current < num_args, ETL_ERROR(bad_format_string_exception)/* not enough arguments for generated index */); + return current++; + } + + ETL_CONSTEXPR14 void check_arg_id(size_t id) + { + // manual index specification only allowed if not already in automatic mode + ETL_ASSERT(automatic_mode == false, ETL_ERROR(bad_format_string_exception)); + manual_mode = true; + ETL_ASSERT(id < num_args, ETL_ERROR(bad_format_string_exception)/* index out of range */); + } + + private: + etl::string_view range; + size_t num_args; + size_t current; + bool automatic_mode; + bool manual_mode; + + template + friend struct formatter; + }; + + using format_parse_context = basic_format_parse_context; + + template + class basic_format_arg + { + public: + + class handle + { + public: + void format(etl::basic_format_parse_context& /* parse_ctx */, + Context& /*format_ctx*/) + { + //typename Context::template formatter_type f; + //parse_ctx.advance_to(f.parse(parse_ctx)); + //format_ctx.advance_to(f.format(const_cast(static_cast(ref)), format_ctx)); + } + + private: + const void* obj; + typedef void(*function_type)(etl::basic_format_parse_context&, Context&, const void*); + function_type func; + }; + + basic_format_arg() + { + } + + basic_format_arg(const bool v) + : data(v) + { + } + + basic_format_arg(const int v) + : data(v) + { + } + + basic_format_arg(const unsigned int v) + : data(v) + { + } + + basic_format_arg(const long long int v) + : data(v) + { + } + + basic_format_arg(const unsigned long long int v) + : data(v) + { + } + + basic_format_arg(const char* v) + : data(v) + { + } + + basic_format_arg(char v) + : data(v) + { + } + + basic_format_arg(const float v) + : data(v) + { + } + + basic_format_arg(const double v) + : data(v) + { + } + + basic_format_arg(const long double v) + : data(v) + { + } + + basic_format_arg(const etl::string_view v) + : data(v) + { + } + + basic_format_arg(const basic_format_arg& other): data(other.data) + { + } + + basic_format_arg(const void* v) + : data(v) + { + } + + basic_format_arg& operator=(const basic_format_arg& other) + { + data = other.data; + return *this; + } + + explicit operator bool() const + { + return !etl::holds_alternative(data); + } + + template + R visit(Visitor&& vis) + { + return etl::visit(etl::forward(vis), data); + } + + private: + supported_format_types data; + }; + + template + class format_arg_store + { + public: + format_arg_store(Args&... args): _args{args...} {} + + basic_format_arg get(size_t i) const + { + return _args.get(i); + } + + etl::array_view> get() + { + return _args; + } + + private: + etl::array, sizeof...(Args)> _args; + }; + + template + class basic_format_args + { + public: + template + basic_format_args(format_arg_store& store): _args(store.get()) + { + } + + basic_format_args(const basic_format_args& other): _args(other._args) + { + } + + basic_format_args& operator=(const basic_format_args& other) + { + _args = other._args; + return *this; + } + + basic_format_arg get(size_t i) const + { + return _args[i]; + } + + // non-standard + size_t size() + { + return _args.size(); + } + + private: + etl::array_view> _args; + }; + + namespace private_format + { + using char_type = char; + + enum class spec_align_t + { + NONE, // default + START, + END, + CENTER + }; + + enum class spec_sign_t + { + MINUS, // default + PLUS, + SPACE + }; + + struct format_spec_t + { + etl::optional index{etl::nullopt_t()}; + spec_align_t align{spec_align_t::NONE}; // '<' / '>' / '^' / none (default) + char_type fill{' '}; // fill character (' ' is default) + spec_sign_t sign{spec_sign_t::MINUS}; // '+' / '-' (default) / ' ' + bool hash{false}; // # + bool zero{false}; // 0 + etl::optional width{etl::nullopt_t()}; // the arg index if width_nested_replacement == true + bool width_nested_replacement{false}; // {} + etl::optional precision{etl::nullopt_t()}; // the arg index if precision_nested_replacement == true + bool precision_nested_replacement{false}; // {} + bool locale_specific{false}; // 'L' + etl::optional type{etl::nullopt_t()}; // literal 's', 'b', 'd', ... + }; + } + + template + class basic_format_context + { + public: + + using iterator = OutputIt; + using char_type = CharT; + + basic_format_context(const basic_format_context& other): _it(other._it), _format_args(other._format_args) + { + } + + basic_format_context(OutputIt it, basic_format_args& fmt_args): _it(it), _format_args(fmt_args) + { + } + + basic_format_context& operator= (const basic_format_context&) = delete; + + basic_format_arg arg(size_t id) const + { + return _format_args.get(id); + } + + iterator out() + { + return _it; + } + + void advance_to(iterator it) + { + _it = it; + } + + private_format::format_spec_t format_spec; + + private: + iterator _it; + basic_format_args& _format_args; + }; + + template + using format_context = basic_format_context; + + template + using format_args = basic_format_args>; + + template + using format_arg = basic_format_arg>; + + template, class... Args> + format_arg_store make_format_args(Args&... args) + { + return format_arg_store(args...); + } + + namespace private_format + { + inline bool is_digit(const char c) + { + return c >= '0' && c <= '9'; + } + + inline void advance(format_parse_context& parse_ctx) + { + parse_ctx.advance_to(parse_ctx.begin() + 1); + } + + inline etl::optional parse_num(format_parse_context& parse_ctx) + { + etl::optional result; + auto fmt_it = parse_ctx.begin(); + while (fmt_it != parse_ctx.end()) + { + const char c = *fmt_it; + if (is_digit(c)) + { + size_t old_value = result.value_or(0); + size_t new_value = old_value * 10 + static_cast(c - '0'); + if (new_value < old_value) { + // Overflow detected + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + result = new_value; + } + else + { + break; + } + ++fmt_it; + } + if (result.has_value()) + { + parse_ctx.advance_to(fmt_it); + } + return result; + } + + inline etl::optional parse_any_of(format_parse_context& parse_ctx, etl::string_view chars) + { + etl::optional result; + auto fmt_it = parse_ctx.begin(); + if (fmt_it != parse_ctx.end()) + { + const char c = *fmt_it; + auto it = etl::find(chars.cbegin(), chars.cend(), c); + if (it != chars.cend()) + { + result = *it; + ++fmt_it; + parse_ctx.advance_to(fmt_it); + } + } + return result; + } + + inline bool parse_char(format_parse_context& parse_ctx, char c) + { + auto fmt_it = parse_ctx.begin(); + if (fmt_it != parse_ctx.end()) + { + char value = *fmt_it; + if (value == c) { + ++fmt_it; + parse_ctx.advance_to(fmt_it); + return true; + } + } + return false; + } + + inline bool parse_sequence(format_parse_context& parse_ctx, etl::string_view sequence) + { + auto fmt_it = parse_ctx.begin(); + if (etl::equal(sequence.cbegin(), sequence.cend(), fmt_it)) + { + fmt_it += sequence.size(); + parse_ctx.advance_to(fmt_it); + return true; + } + return false; + } + + inline bool is_align_character(char c) + { + return c == '<' || c == '>' || c == '^'; + } + + inline spec_align_t align_from_char(char c) + { + spec_align_t result = spec_align_t::NONE; + switch (c) + { + case '<': result = spec_align_t::START; break; + case '>': result = spec_align_t::END; break; + case '^': result = spec_align_t::CENTER; break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + return result; + } + + inline spec_align_t parse_fill_and_align(format_parse_context& parse_ctx, char_type& fill) + { + spec_align_t result = spec_align_t::NONE; + fill = ' '; // default + + auto fmt_it = parse_ctx.begin(); + if (fmt_it != parse_ctx.end()) + { + const char c = *fmt_it; + ++fmt_it; + + if (is_align_character(c)) + { + result = align_from_char(c); + parse_ctx.advance_to(fmt_it); + } + else if (fmt_it != parse_ctx.end()) + { + const char c2 = *fmt_it; + ++fmt_it; + if (is_align_character(c2)) + { + result = align_from_char(c2); + ETL_ASSERT(c != '{' && c != '}', ETL_ERROR(bad_format_string_exception)); // no { or } allowed as fill character + fill = c; + parse_ctx.advance_to(fmt_it); + } + } + else + { + // no align and fill spec (valid) + } + } + return result; + } + + inline spec_sign_t sign_from_char(const char c) + { + spec_sign_t result = spec_sign_t::MINUS; + switch (c) + { + case '-': result = spec_sign_t::MINUS; break; + case '+': result = spec_sign_t::PLUS; break; + case ' ': result = spec_sign_t::SPACE; break; + default: + // invalid sign character c + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + return result; + } + + inline bool parse_nested_replacement(format_parse_context& parse_ctx, etl::optional& index) + { + bool start = parse_char(parse_ctx, '{'); + if (start) + { + auto num = parse_num(parse_ctx); + if (num) + { + // manual mode + index = num; + parse_ctx.check_arg_id(*index); + bool end = parse_char(parse_ctx, '}'); + if (end) + { + return true; + } + else + { + // bad nested replacement index spec + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + else + { + bool end = parse_char(parse_ctx, '}'); + if (end) + { + index = parse_ctx.next_arg_id(); + return true; + } + else + { + // bad nested replacement spec + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + return false; + } + + template + void parse_format_spec(format_parse_context& parse_ctx, format_context& fmt_context) + { + auto& format_spec = fmt_context.format_spec; + + format_spec = format_spec_t(); // reset format_spec to defaults + + format_spec.index = parse_num(parse_ctx); // optional + + bool colon = parse_char(parse_ctx, ':'); + if (colon) + { + format_spec.align = parse_fill_and_align(parse_ctx, format_spec.fill); + + etl::optional sign = parse_any_of(parse_ctx, "+- "); + if (sign) { + format_spec.sign = sign_from_char(*sign); + } + + format_spec.hash = parse_char(parse_ctx, '#'); + format_spec.zero = parse_char(parse_ctx, '0'); + + format_spec.width = parse_num(parse_ctx); + if (!format_spec.width) + { + // possibly with via nested replacement + format_spec.width_nested_replacement = parse_nested_replacement(parse_ctx, format_spec.width); + } + + if (parse_char(parse_ctx, '.')) + { + format_spec.precision = parse_num(parse_ctx); + if (!format_spec.precision) + { + // possibly with via nested replacement + format_spec.precision_nested_replacement = parse_nested_replacement(parse_ctx, format_spec.precision); + } + } + + format_spec.locale_specific = parse_char(parse_ctx, 'L'); + + format_spec.type = parse_any_of(parse_ctx, "s?bBcdoxXaAeEfFgGpP"); + } + } + } + + template + struct formatter + { + using char_type = CharT; + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + return parse_ctx.end(); + } + + template + typename format_context::iterator format(etl::monostate arg, format_context& fmt_ctx) + { + (void)arg; + return fmt_ctx.out(); + } + }; + + namespace private_format + { + // for 4321, return 1000 + template::value>> + UnsignedT get_highest_digit(UnsignedT value, size_t base = 10) + { + ETL_ASSERT(base > 1, ETL_ERROR(bad_format_string_exception)); + UnsignedT result = 1; + value /= base; + while (result <= value) + { + result *= base; + } + return result; + } + + template + T int_pow(T base, T exp) + { + T result = 1; + while (exp > 0) { + if (exp % 2 == 1) result *= base; + base *= base; + exp /= 2; + } + return result; + } + + template + void format_sign(OutputIt& it, T value, const format_spec_t& spec) + { + char c = '\0'; + if (value < 0) + { + c = '-'; + } + else + { + switch (spec.sign) + { + case spec_sign_t::MINUS: + // c already set above if negative + break; + case spec_sign_t::PLUS: + c = '+'; + break; + case spec_sign_t::SPACE: + c = ' '; + break; + default: + // invalid sign + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + + if (c != '\0') + { + *it = c; + ++it; + } + } + + template + void format_sequence(OutputIt& out_it, etl::string_view value) + { + auto it = value.cbegin(); + while (it != value.cend()) + { + *out_it = *it; + ++it; + ++out_it; + } + } + + template + void format_alternate_form(OutputIt& it, const format_spec_t& spec) + { + if (spec.hash && spec.type.has_value()) + { + switch (spec.type.value()) + { + case 'b': + format_sequence(it, "0b"); + break; + case 'B': + format_sequence(it, "0B"); + break; + case 'o': + format_sequence(it, "0"); + break; + case 'x': + format_sequence(it, "0x"); + break; + case 'X': + format_sequence(it, "0X"); + break; + // default: no prefix + } + } + } + + template + void format_plain_char(OutputIt& it, char_type c) + { + *it = c; + ++it; + } + + template + void format_escaped_char(OutputIt& it, char_type c) + { + switch (c) + { + case '\t': + format_sequence(it, "\\t"); + break; + case '\n': + format_sequence(it, "\\n"); + break; + case '\r': + format_sequence(it, "\\r"); + break; + case '"': + format_sequence(it, "\\\""); + break; + case '\'': + format_sequence(it, "\\'"); + break; + case '\\': + format_sequence(it, "\\\\"); + break; + default: + *it = c; + ++it; + } + } + + template + void fill(OutputIt& it, size_t size, char_type c) + { + while (size > 0) + { + *it = c; + ++it; + --size; + } + } + + template + inline size_t base_from_spec(const format_spec_t& spec) + { + size_t base = default_base; + if (spec.type.has_value()) + { + switch (spec.type.value()) + { + case 'a': + case 'A': + base = 16; + break; + case 'b': + case 'B': + base = 2; + break; + case 'o': + base = 8; + break; + case 'p': + case 'P': + case 'x': + case 'X': + base = 16; + break; + // default: no prefix + } + } + return base; + } + + inline bool is_uppercase(const char c) + { + return c >= 'A' && c <= 'Z'; + } + + template + void format_digit_char(OutputIt& it, T value, const format_spec_t& spec) + { + if (value <= 9) + { + *it = '0' + value; + } + else + { + if (spec.type.has_value() && is_uppercase(spec.type.value())) + { + *it = 'A' + (value - 10); + } + else + { + *it = 'a' + (value - 10); + } + } + ++it; + } + + inline void adjust_width_from_spec(const format_spec_t& spec, size_t& width) + { + if (spec.zero && spec.width.has_value()) + { + width = etl::max(width, spec.width.value()); + } + } + + inline void check_precision(const format_spec_t& spec) + { + if (spec.precision.has_value()) + { + // precision not allowed for integer numbers + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + + // used for both integers and float parts + // skip_last_zeros helps in case of printing after-the-decimal zeros which are + // redundant then + template + void format_plain_num(OutputIt& it, T value, const format_spec_t& spec, size_t width = 0) + { + using UnsignedT = typename etl::make_unsigned::type; + + UnsignedT unsigned_value = static_cast(value < 0 ? -value : value); + + size_t base = base_from_spec(spec); + UnsignedT highest_digit = get_highest_digit(unsigned_value, base); + if (width > 0) + { + UnsignedT align_highest_digit = int_pow(base, width - 1); + highest_digit = etl::max(align_highest_digit, highest_digit); + } + + // this loop is iterated at least once, to print a number + while (highest_digit > 0) + { + UnsignedT digit = unsigned_value / highest_digit; + unsigned_value %= highest_digit; + format_digit_char(it, digit, spec); + + if ETL_CONSTEXPR17 (skip_last_zeros) + { + if (unsigned_value == 0) + { + break; + } + } + + highest_digit /= base; + } + } + + // for integers + template + void format_num(OutputIt& it, T value, const format_spec_t& spec) + { + size_t width = 0; + format_sign(it, value, spec); + format_alternate_form(it, spec); + adjust_width_from_spec(spec, width); + check_precision(spec); + format_plain_num(it, value, spec, width); + } + + template + void format_floating_default(OutputIt& it, T value, const format_spec_t& spec) + { + const size_t fractional_decimals = 6; // default + + T integral; + T fractional = modf(value, &integral); + bool sign; + unsigned long long int fractional_int; + unsigned long long int integral_int; + if (integral < 0.0) + { + sign = true; + fractional_int = static_cast(-fractional * pow(10., fractional_decimals)); + integral_int = static_cast(-integral); + } + else + { + sign = false; + fractional_int = static_cast(fractional * pow(10., fractional_decimals)); + integral_int = static_cast(integral); + } + + private_format::format_sign(it, sign ? -1 : 0, spec); + private_format::format_plain_num(it, integral_int, spec); + private_format::format_sequence(it, "."); + private_format::format_plain_num(it, fractional_int, spec, fractional_decimals); + } + + // floating point in hex notation + template + void format_floating_a(OutputIt& it, T value, const format_spec_t& spec) + { + static const size_t fractional_decimals = 10; // default + static const size_t exponent_decimals = 1; + long long int exponent_int = 0; + + bool sign; + unsigned long long int fractional_int; + unsigned long long int integral_int; + + T integral; + T fractional = modf(value, &integral); + + while (value >= 0x10 || value <= -0x10) + { + ++exponent_int; + value /= 0x10; + fractional = modf(value, &integral); + } + + while ((value > 0.0000000000001 && value < 1) || (value < -0.0000000000001 && value > -1)) + { + --exponent_int; + value *= 0x10; + fractional = modf(value, &integral); + } + + if (integral < 0.0) + { + sign = true; + fractional_int = static_cast(-fractional * pow(static_cast(0x10), fractional_decimals)); + integral_int = static_cast(-integral); + } + else + { + sign = false; + fractional_int = static_cast(fractional * pow(static_cast(0x10), fractional_decimals)); + integral_int = static_cast(integral); + } + + private_format::format_sign(it, sign ? -1 : 0, spec); + private_format::format_plain_char(it, '0'); + char hex_letter = 'x'; + if (is_uppercase(spec.type.value())) + { + hex_letter = 'X'; + } + private_format::format_plain_char(it, hex_letter); + private_format::format_plain_num(it, integral_int, spec); + private_format::format_plain_char(it, '.'); + private_format::format_plain_num(it, fractional_int, spec, fractional_decimals); + char letter = 'p'; + if (is_uppercase(spec.type.value())) + { + letter = 'P'; + } + private_format::format_plain_char(it, letter); + private_format::format_plain_char(it, (exponent_int < 0) ? '-' : '+'); + private_format::format_plain_num(it, exponent_int, spec, exponent_decimals); + } + + template + void format_floating_e(OutputIt& it, T value, const format_spec_t& spec) + { + static const size_t fractional_decimals = 6; // default + static const size_t exponent_decimals = 2; + long long int exponent_int = 0; + + bool sign; + unsigned long long int fractional_int; + unsigned long long int integral_int; + + T integral; + T fractional = modf(value, &integral); + + while (value >= 10 || value <= -10) + { + ++exponent_int; + value /= 10; + fractional = modf(value, &integral); + } + + while ((value > 0.0000000000001 && value < 1) || (value < -0.0000000000001 && value > -1)) + { + --exponent_int; + value *= 10; + fractional = modf(value, &integral); + } + + if (integral < 0.0) + { + sign = true; + fractional_int = static_cast(-fractional * pow(10., fractional_decimals)); + integral_int = static_cast(-integral); + } + else + { + sign = false; + fractional_int = static_cast(fractional * pow(10., fractional_decimals)); + integral_int = static_cast(integral); + } + + private_format::format_sign(it, sign ? -1 : 0, spec); + private_format::format_plain_num(it, integral_int, spec); + private_format::format_sequence(it, "."); + private_format::format_plain_num(it, fractional_int, spec, fractional_decimals); + char letter = 'e'; + if (is_uppercase(spec.type.value())) + { + letter = 'E'; + } + private_format::format_plain_char(it, letter); + private_format::format_plain_char(it, (exponent_int < 0) ? '-' : '+'); + private_format::format_plain_num(it, exponent_int, spec, exponent_decimals); + } + + template + void format_floating_f(OutputIt& it, T value, const format_spec_t& spec) + { + const size_t fractional_decimals = 6; // default + + T integral; + T fractional = modf(value, &integral); + bool sign; + unsigned long long int fractional_int; + unsigned long long int integral_int; + if (integral < 0.0) + { + sign = true; + fractional_int = static_cast(-fractional * pow(10., fractional_decimals)); + integral_int = static_cast(-integral); + } + else + { + sign = false; + fractional_int = static_cast(fractional * pow(10., fractional_decimals)); + integral_int = static_cast(integral); + } + + private_format::format_sign(it, sign ? -1 : 0, spec); + private_format::format_plain_num(it, integral_int, spec); + private_format::format_sequence(it, "."); + private_format::format_plain_num(it, fractional_int, spec, fractional_decimals); + } + + class dummy_assign_to + { + public: + dummy_assign_to& operator=(char_type) + { + return *this; + } + }; + + template + class limit_assign_to + { + public: + limit_assign_to(OutputIt o, bool is_active) + : out(o) + , active(is_active) + { + } + + limit_assign_to& operator=(char_type c) + { + if (active) + { + *out = c; + } + return *this; + } + + private: + OutputIt out; + bool active; + }; + + template + class limit_iterator + { + public: + limit_iterator(OutputIt& it, size_t n) + : out(it) + , limit(n) + { + } + + limit_iterator(const limit_iterator& other) = default; + limit_iterator(limit_iterator&& other) = default; + limit_iterator& operator=(const limit_iterator& other) = default; + limit_iterator& operator=(limit_iterator&& other) = default; + + limit_assign_to operator*() + { + return limit_assign_to(out, (limit > 0)); + } + + limit_iterator& operator++() + { + if (limit > 0) + { + --limit; + ++out; + } + return *this; + } + + limit_iterator operator++(int) + { + limit_iterator temp = *this; + if (limit > 0) + { + --limit; + out++; + } + return temp; + } + + OutputIt get() + { + return out; + } + + private: + OutputIt out; + size_t limit; + }; + + class counter_iterator + { + public: + counter_iterator(): count(0) + { + } + + counter_iterator(const counter_iterator& other) = default; + counter_iterator& operator=(const counter_iterator& other) = default; + + dummy_assign_to operator*() + { + return dummy_assign_to(); + } + + counter_iterator& operator++() + { + ++count; + return *this; + } + + counter_iterator operator++(int) + { + counter_iterator temp = *this; + count++; + return temp; + } + + size_t value() + { + return count; + } + + private: + size_t count; + }; + + template + void format_floating_g(OutputIt& it, T value, const format_spec_t& spec) + { + private_format::counter_iterator counter_e, counter_f; + + format_floating_e(counter_e, value, spec); + format_floating_f(counter_f, value, spec); + + if (counter_e.value() < counter_f.value()) + { + format_floating_e(it, value, spec); + } + else + { + format_floating_f(it, value, spec); + } + } + + template + void format_floating(OutputIt& it, T value, const format_spec_t& spec) + { + if (isnan(value)) + { + if (spec.type.has_value() && (is_uppercase(spec.type.value()))) + { + format_sequence(it, "NAN"); + } + else + { + format_sequence(it, "nan"); + } + } + else if (isinf(value)) + { + if (spec.type.has_value() && (is_uppercase(spec.type.value()))) + { + format_sequence(it, "INF"); + } + else + { + format_sequence(it, "inf"); + } + } + else if (!spec.type.has_value()) + { + format_floating_default(it, value, spec); + } + else + { + switch (spec.type.value()) + { + case 'a': + case 'A': + format_floating_a(it, value, spec); + break; + case 'e': + case 'E': + format_floating_e(it, value, spec); + break; + case 'f': + case 'F': + format_floating_f(it, value, spec); + break; + case 'g': + case 'G': + format_floating_g(it, value, spec); + break; + default: + // unknown presentation type + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + template + struct format_visitor + { + using output_iterator = OutputIt; + + format_visitor(format_parse_context& parse_context, format_context& f_ctx) + : parse_ctx(parse_context) + , fmt_ctx(f_ctx) + { + } + + // for all types in supported_format_types + template + void operator()(T value) + { + formatter f; + format_parse_context::iterator it = f.parse(parse_ctx); + parse_ctx.advance_to(it); + OutputIt fit = f.format(value, fmt_ctx); + fmt_ctx.advance_to(fit); + } + + format_parse_context& parse_ctx; + format_context& fmt_ctx; + }; + + template + void output(format_context& fmt_context, char c) + { + *fmt_context.out() = c; + OutputIt tmp = fmt_context.out(); + tmp++; + fmt_context.advance_to(tmp); + } + + template + typename format_context::iterator format_aligned_int(Int arg, format_context& fmt_ctx) + { + size_t prefix_size = 0; + size_t suffix_size = 0; + + if (fmt_ctx.format_spec.width) + { + // calculate size + private_format::counter_iterator counter; + private_format::format_num(counter, arg, fmt_ctx.format_spec); + + if (counter.value() < fmt_ctx.format_spec.width.value()) + { + size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); + switch (fmt_ctx.format_spec.align) + { + case private_format::spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case private_format::spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case private_format::spec_align_t::NONE: // default + case private_format::spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + // actual output + OutputIt it = fmt_ctx.out(); + private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); + private_format::format_num(it, arg, fmt_ctx.format_spec); + private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); + return it; + } + + template + typename format_context::iterator format_aligned_floating(Float arg, format_context& fmt_ctx) + { + size_t prefix_size = 0; + size_t suffix_size = 0; + + if (fmt_ctx.format_spec.width) + { + // calculate size + private_format::counter_iterator counter; + private_format::format_floating(counter, arg, fmt_ctx.format_spec); + + if (counter.value() < fmt_ctx.format_spec.width.value()) + { + size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); + switch (fmt_ctx.format_spec.align) + { + case private_format::spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case private_format::spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case private_format::spec_align_t::NONE: // default + case private_format::spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + // actual output + OutputIt it = fmt_ctx.out(); + private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); + private_format::format_floating(it, arg, fmt_ctx.format_spec); + private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); + return it; + } + + template + void format_string_view(OutputIt& it, etl::string_view arg, const format_spec_t& spec) + { + bool escaped = false; + if (spec.type.has_value()) + { + switch (spec.type.value()) + { + case 's': + // default output + break; + case '?': + // escaped string + escaped = true; + break; + default: + // invalid type for string + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + size_t limit = etl::numeric_limits::max(); + if (spec.precision.has_value()) + { + limit = spec.precision.value(); + } + + if (escaped) + { + format_plain_char(it, '"'); + } + etl::string_view::const_iterator arg_it = arg.begin(); + while (arg_it != arg.cend() && limit > 0) + { + if (escaped) + { + format_escaped_char(it, *arg_it); + } + else + { + format_plain_char(it, *arg_it); + } + ++arg_it; + --limit; + } + if (escaped) + { + format_plain_char(it, '"'); + } + } + + template + typename format_context::iterator format_aligned_string_view(etl::string_view arg, format_context& fmt_ctx) + { + size_t prefix_size = 0; + size_t suffix_size = 0; + + if (fmt_ctx.format_spec.width) + { + // calculate size + private_format::counter_iterator counter; + private_format::format_string_view(counter, arg, fmt_ctx.format_spec); + + if (counter.value() < fmt_ctx.format_spec.width.value()) + { + size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); + switch (fmt_ctx.format_spec.align) + { + case private_format::spec_align_t::NONE: // default + case private_format::spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case private_format::spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case private_format::spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + // actual output + OutputIt it = fmt_ctx.out(); + private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); + private_format::format_string_view(it, arg, fmt_ctx.format_spec); + private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); + return it; + } + + template + void format_chars(OutputIt& it, const char* arg, const format_spec_t& spec) + { + bool escaped = false; + if (spec.type.has_value()) + { + switch (spec.type.value()) + { + case 's': + // default output + break; + case '?': + // escaped string + escaped = true; + break; + default: + // invalid type for string + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + size_t limit = etl::numeric_limits::max(); + if (spec.precision.has_value()) + { + limit = spec.precision.value(); + } + + if (escaped) + { + format_plain_char(it, '"'); + } + const char_type* arg_it = arg; + while (*arg_it != '\0' && limit > 0) + { + if (escaped) + { + format_escaped_char(it, *arg_it); + } + else + { + format_plain_char(it, *arg_it); + } + ++arg_it; + --limit; + } + if (escaped) + { + format_plain_char(it, '"'); + } + } + + template + typename format_context::iterator format_aligned_chars(const char* arg, format_context& fmt_ctx) + { + size_t prefix_size = 0; + size_t suffix_size = 0; + + if (fmt_ctx.format_spec.width) + { + // calculate size + private_format::counter_iterator counter; + private_format::format_chars(counter, arg, fmt_ctx.format_spec); + + if (counter.value() < fmt_ctx.format_spec.width.value()) + { + size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); + switch (fmt_ctx.format_spec.align) + { + case private_format::spec_align_t::NONE: // default + case private_format::spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case private_format::spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case private_format::spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + // actual output + OutputIt it = fmt_ctx.out(); + private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); + private_format::format_chars(it, arg, fmt_ctx.format_spec); + private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); + return it; + } + + inline void check_char_spec(const format_spec_t& spec) + { + if ((!spec.type.has_value() || spec.type.value() == 'c' || spec.type.value() == '?') && + (spec.sign != spec_sign_t::MINUS || + spec.zero || + spec.hash || + spec.precision) + ) + { + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + + template + void format_char(OutputIt& it, char_type c, const format_spec_t& spec) + { + check_char_spec(spec); + if (spec.type.has_value()) + { + switch (spec.type.value()) + { + case 'c': + // default output + format_plain_char(it, c); + break; + case '?': + // escaped string + format_plain_char(it, '\''); + format_escaped_char(it, c); + format_plain_char(it, '\''); + break; + case 'b': + case 'B': + case 'd': + case 'o': + case 'x': + case 'X': + private_format::format_num(it, static_cast(static_cast(c)), spec); + break; + default: + // invalid type for string + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + else + { + format_plain_char(it, c); + } + } + + template + typename format_context::iterator format_aligned_char(char_type arg, format_context& fmt_ctx) + { + size_t prefix_size = 0; + size_t suffix_size = 0; + + if (fmt_ctx.format_spec.width) + { + // calculate size + private_format::counter_iterator counter; + private_format::format_char(counter, arg, fmt_ctx.format_spec); + + if (counter.value() < fmt_ctx.format_spec.width.value()) + { + size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); + switch (fmt_ctx.format_spec.align) + { + case private_format::spec_align_t::NONE: // default + if (!fmt_ctx.format_spec.type.has_value() || fmt_ctx.format_spec.type.value() == 'c' || fmt_ctx.format_spec.type.value() == '?') + { + prefix_size = 0; + suffix_size = pad; + } + else + { + prefix_size = pad; + suffix_size = 0; + } + break; + case private_format::spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case private_format::spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case private_format::spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + // actual output + OutputIt it = fmt_ctx.out(); + private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); + private_format::format_char(it, arg, fmt_ctx.format_spec); + private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); + return it; + } + + template + void format_bool(OutputIt& it, bool value, const format_spec_t& spec) + { + if (spec.type.has_value()) + { + switch (spec.type.value()) + { + case 's': + // default output + format_sequence(it, value ? "true" : "false"); + break; + case 'b': + case 'B': + case 'd': + case 'o': + case 'x': + case 'X': + private_format::format_num(it, static_cast(static_cast(value)), spec); + break; + default: + // invalid type for string + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + else + { + format_sequence(it, value ? "true" : "false"); + } + } + + template + typename format_context::iterator format_aligned_bool(bool arg, format_context& fmt_ctx) + { + size_t prefix_size = 0; + size_t suffix_size = 0; + + if (fmt_ctx.format_spec.width) + { + // calculate size + private_format::counter_iterator counter; + private_format::format_bool(counter, arg, fmt_ctx.format_spec); + + if (counter.value() < fmt_ctx.format_spec.width.value()) + { + size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); + switch (fmt_ctx.format_spec.align) + { + case private_format::spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case private_format::spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case private_format::spec_align_t::NONE: // default + case private_format::spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + // actual output + OutputIt it = fmt_ctx.out(); + private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); + private_format::format_bool(it, arg, fmt_ctx.format_spec); + private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); + return it; + } + + template + void format_pointer(OutputIt& it, const void* value, const format_spec_t& spec) + { + if (spec.type.has_value()) + { + switch (spec.type.value()) + { + case 'p': + case 'P': + format_sequence(it, spec.type.value() == 'p' ? "0x" : "0X"); + format_plain_num(it, reinterpret_cast(value), spec); + break; + default: + // invalid type for string + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + else + { + format_sequence(it, "0x"); + format_plain_num(it, reinterpret_cast(value), spec); + } + } + + template + typename format_context::iterator format_aligned_pointer(const void* arg, format_context& fmt_ctx) + { + size_t prefix_size = 0; + size_t suffix_size = 0; + + if (fmt_ctx.format_spec.width) + { + // calculate size + private_format::counter_iterator counter; + private_format::format_pointer(counter, arg, fmt_ctx.format_spec); + + if (counter.value() < fmt_ctx.format_spec.width.value()) + { + size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); + switch (fmt_ctx.format_spec.align) + { + case private_format::spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case private_format::spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case private_format::spec_align_t::NONE: // default + case private_format::spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + default: + // invalid alignment specification + ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); + } + } + } + + // actual output + OutputIt it = fmt_ctx.out(); + private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); + private_format::format_pointer(it, arg, fmt_ctx.format_spec); + private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); + return it; + } + } // namespace private_format + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(int arg, format_context& fmt_ctx) + { + if (fmt_ctx.format_spec.type.has_value() && fmt_ctx.format_spec.type.value() == 'c') + { + return private_format::format_aligned_char(static_cast(arg), fmt_ctx); + } + return private_format::format_aligned_int(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(unsigned int arg, format_context& fmt_ctx) + { + if (fmt_ctx.format_spec.type.has_value() && fmt_ctx.format_spec.type.value() == 'c') + { + return private_format::format_aligned_char(static_cast(arg), fmt_ctx); + } + return private_format::format_aligned_int(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(long long int arg, format_context& fmt_ctx) + { + if (fmt_ctx.format_spec.type.has_value() && fmt_ctx.format_spec.type.value() == 'c') + { + return private_format::format_aligned_char(static_cast(arg), fmt_ctx); + } + return private_format::format_aligned_int(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(unsigned long long int arg, format_context& fmt_ctx) + { + if (fmt_ctx.format_spec.type.has_value() && fmt_ctx.format_spec.type.value() == 'c') + { + return private_format::format_aligned_char(static_cast(arg), fmt_ctx); + } + return private_format::format_aligned_int(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(private_format::char_type arg, format_context& fmt_ctx) + { + return private_format::format_aligned_char(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(float arg, format_context& fmt_ctx) + { + return private_format::format_aligned_floating(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(double arg, format_context& fmt_ctx) + { + return private_format::format_aligned_floating(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(long double arg, format_context& fmt_ctx) + { + return private_format::format_aligned_floating(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(etl::string_view arg, format_context& fmt_ctx) + { + return private_format::format_aligned_string_view(arg, fmt_ctx); + } + }; + + // string formatter + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(const char* arg, format_context& fmt_ctx) + { + return private_format::format_aligned_chars(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(bool arg, format_context& fmt_ctx) + { + return private_format::format_aligned_bool(arg, fmt_ctx); + } + }; + + template<> + struct formatter + { + format_parse_context::iterator parse(format_parse_context& parse_ctx) + { + // unified parsing is done already in vformat_to() + return parse_ctx.begin(); + } + + template + typename format_context::iterator format(const void* arg, format_context& fmt_ctx) + { + return private_format::format_aligned_pointer(arg, fmt_ctx); + } + }; + + template + OutputIt vformat_to(OutputIt out, etl::string_view fmt, format_args args) + { + format_parse_context parse_context(fmt, args.size()); + format_context fmt_context(out, args); + private_format::format_visitor v(parse_context, fmt_context); + + while (parse_context.begin() != parse_context.end()) + { + const char c = *parse_context.begin(); + private_format::advance(parse_context); + if (c == '{') + { + + if (*parse_context.begin() == '{') + { + // escape sequence for literal '{' + private_format::output(fmt_context, c); + private_format::advance(parse_context); + } + else + { + private_format::parse_format_spec(parse_context, fmt_context); + etl::optional index = fmt_context.format_spec.index; + if (index.has_value()) + { + parse_context.check_arg_id(*index); + } + else + { + index = parse_context.next_arg_id(); + } + format_arg arg = args.get(*index); + arg.template visit(v); + + ETL_ASSERT(*parse_context.begin() == '}', ETL_ERROR(bad_format_string_exception)/*"Closing brace missing"*/); + if (parse_context.begin() != parse_context.end()) + { + private_format::advance(parse_context); + } + } + } + else if (c == '}') // only matches here if } without { is found + { + ETL_ASSERT(*parse_context.begin() == '}', ETL_ERROR(bad_format_string_exception)/*"2nd closing brace missing on escaped closing brace"*/); + // escape sequence for literal '}' + private_format::output(fmt_context, c); + private_format::advance(parse_context); + } + else + { + private_format::output(fmt_context, c); + } + }; + + // plain strings need it, back insert iterators don't: + //*fmt_context.out() = '\0'; + + return fmt_context.out(); + } + + template::type, OutputIt>::value>, + class... Args> + OutputIt format_to(OutputIt out, format_string fmt, Args&&... args) + { + auto the_args{make_format_args(args...)}; + return vformat_to(etl::move(out), fmt.get(), format_args(the_args)); + } + + template, class... Args> + OutputIt format_to_n(OutputIt out, size_t n, format_string fmt, Args&&... args) + { + auto the_args{make_format_args(args...)}; + return vformat_to(WrapperIt(out, n), + fmt.get(), + format_args(the_args)).get(); + } + + // non std in the following, specific to etl + template + etl::istring::iterator format_to(etl::istring& out, format_string fmt, Args&&... args) + { + etl::istring::iterator result = format_to_n(out.begin(), out.max_size(), fmt, etl::forward(args)...); + out.uninitialized_resize(result - out.begin()); + return result; + } + + template + size_t formatted_size(format_string fmt, Args&&... args) + { + private_format::counter_iterator it; + it = format_to(it, fmt, etl::forward(args)...); + return it.value(); + } +} + +#endif + +#endif diff --git a/include/etl/print.h b/include/etl/print.h new file mode 100644 index 00000000..511fab70 --- /dev/null +++ b/include/etl/print.h @@ -0,0 +1,117 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 BMW AG + +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. +******************************************************************************/ + +#ifndef ETL_PRINT_INCLUDED +#define ETL_PRINT_INCLUDED + +#include "platform.h" + +#include "format.h" + +#if ETL_USING_CPP11 + +// to be implemented in a concrete project, typically printing to a serial console +// type int here is the convention from putchar(), actually storing char +extern "C" void etl_putchar(int c); + +namespace etl +{ + namespace private_print + { + using char_type = etl::private_format::char_type; + + // No-op iterator that forwards all assignments to etl_putchar + class print_iterator + { + public: + class print_to + { + public: + print_to& operator=(char_type c) + { + etl_putchar(static_cast(c)); + return *this; + } + }; + + print_iterator() + { + } + + print_iterator(const print_iterator&) + { + } + + print_iterator& operator=(const print_iterator&) + { + return *this; + } + + print_to operator*() + { + return print_to(); + } + + print_iterator& operator++() + { + return *this; + } + + print_iterator operator++(int) + { + return *this; + } + }; + } // namespace private_print + + template + void print(etl::format_string fmt, Args&&... args) + { + private_print::print_iterator it; + (void)format_to(it, etl::move(fmt), etl::forward(args)...); + } + + inline void println() + { + etl_putchar(static_cast('\n')); + } + + template + void println(etl::format_string fmt, Args&&... args) + { + private_print::print_iterator it; + (void)format_to(it, etl::move(fmt), etl::forward(args)...); + println(); + } +} // namespace etl + +#endif + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 624f38d2..babe0e99 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -183,6 +183,7 @@ add_executable(etl_tests test_flat_multiset.cpp test_flat_set.cpp test_fnv_1.cpp + test_format.cpp test_format_spec.cpp test_forward_list.cpp test_forward_list_shared_pool.cpp @@ -258,6 +259,7 @@ add_executable(etl_tests test_poly_span_fixed_extent.cpp test_pool.cpp test_pool_external_buffer.cpp + test_print.cpp test_priority_queue.cpp test_pseudo_moving_average.cpp test_quantize.cpp diff --git a/test/test_format.cpp b/test/test_format.cpp new file mode 100644 index 00000000..54f12823 --- /dev/null +++ b/test/test_format.cpp @@ -0,0 +1,490 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 BMW AG + +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. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/format.h" + +#include "etl/iterator.h" + +#if ETL_USING_CPP11 + +namespace +{ + using iterator = etl::back_insert_iterator; + + template + etl::istring& test_format(etl::istring& s, etl::format_string fmt, Args&&... args) + { + (void) etl::format_to(s, fmt, etl::forward(args)...); + return s; + } + + SUITE(test_format) + { + + //************************************************************************* + TEST(test_empty) + { + etl::string<100> s; + + const char* result = etl::format_to(s, ""); + CHECK_EQUAL(s.cbegin(), result); + CHECK_EQUAL("", s); + } + + //************************************************************************* + TEST(test_format) + { + etl::string<100> s; + + const char* result = etl::format_to(s, "abc"); + CHECK_EQUAL(s.cbegin() + 3, result); + CHECK_EQUAL("abc", s); + } + + //************************************************************************* + TEST(test_format_int) + { + etl::string<100> s; + + CHECK_EQUAL("1", test_format(s, "{}", 1)); + CHECK_EQUAL("123", test_format(s, "{}", 123)); + CHECK_EQUAL("4123", test_format(s, "{}", 4123)); + CHECK_EQUAL("1048962123", test_format(s, "{}", 1048962123)); + CHECK_EQUAL("1 2", test_format(s, "{} {}", 1, 2)); + CHECK_EQUAL("-123", test_format(s, "{}", -123)); + CHECK_EQUAL("-314748364", test_format(s, "{}", (int)-314748364)); + CHECK_EQUAL("2147483647", test_format(s, "{}", (int)2147483647)); + CHECK_EQUAL("-2147483648", test_format(s, "{}", (int)-2147483648)); + CHECK_EQUAL("0", test_format(s, "{}", 0)); + CHECK_EQUAL("-1", test_format(s, "{}", -1)); + } + + //************************************************************************* + TEST(test_format_unsigned_int) + { + etl::string<100> s; + + CHECK_EQUAL("0", test_format(s, "{}", static_cast(0U))); + CHECK_EQUAL("1", test_format(s, "{}", static_cast(1U))); + CHECK_EQUAL("12345678", test_format(s, "{}", static_cast(12345678U))); + CHECK_EQUAL("4123456780", test_format(s, "{}", static_cast(4123456780U))); + } + + //************************************************************************* + TEST(test_format_long_long_int) + { + etl::string<100> s; + + CHECK_EQUAL("-1", test_format(s, "{}", static_cast(-1LL))); + CHECK_EQUAL("0", test_format(s, "{}", static_cast(0LL))); + CHECK_EQUAL("1", test_format(s, "{}", static_cast(1LL))); + CHECK_EQUAL("-12345678", test_format(s, "{}", static_cast(-12345678LL))); + CHECK_EQUAL("-4123456780", test_format(s, "{}", static_cast(-4123456780LL))); + CHECK_EQUAL("-123456781234", test_format(s, "{}", static_cast(-123456781234LL))); + CHECK_EQUAL("-412345678012", test_format(s, "{}", static_cast(-412345678012LL))); + CHECK_EQUAL("12345678", test_format(s, "{}", static_cast(12345678LL))); + CHECK_EQUAL("4123456780", test_format(s, "{}", static_cast(4123456780LL))); + } + + //************************************************************************* + TEST(test_format_unsigned_long_long_int) + { + etl::string<100> s; + + CHECK_EQUAL("0", test_format(s, "{}", static_cast(0LL))); + CHECK_EQUAL("1", test_format(s, "{}", static_cast(1LL))); + CHECK_EQUAL("12345678", test_format(s, "{}", static_cast(12345678LL))); + CHECK_EQUAL("4123456780", test_format(s, "{}", static_cast(4123456780LL))); + CHECK_EQUAL("18446744073709551615", test_format(s, "{}", static_cast(18446744073709551615ULL))); + CHECK_EQUAL("1311768467463790320", test_format(s, "{}", static_cast(0x123456789ABCDEF0ULL))); + } + + //************************************************************************* + TEST(test_format_other_int) + { + etl::string<100> s; + + CHECK_EQUAL("34", test_format(s, "{}", static_cast(34))); + CHECK_EQUAL("-14", test_format(s, "{}", static_cast(-14))); + CHECK_EQUAL("6534", test_format(s, "{}", static_cast(6534))); + CHECK_EQUAL("-9414", test_format(s, "{}", static_cast(-9414))); + CHECK_EQUAL("236534", test_format(s, "{}", static_cast(236534))); + CHECK_EQUAL("-6759414", test_format(s, "{}", static_cast(-6759414))); + } + + //************************************************************************* + TEST(test_format_float) + { + etl::string<100> s; + + CHECK_EQUAL("1.0", test_format(s, "{}", 1.0f)); + CHECK_EQUAL("1.234567", test_format(s, "{}", 1.234567f)); + CHECK_EQUAL("1.234567", test_format(s, "{}", 1.2345678f)); + CHECK_EQUAL("1.125", test_format(s, "{}", 1.125f)); + } + + //************************************************************************* + TEST(test_format_double) + { + etl::string<100> s; + + CHECK_EQUAL("1.0", test_format(s, "{}", 1.0)); + CHECK_EQUAL("1.234564", test_format(s, "{}", 1.234564)); + CHECK_EQUAL("1.234567", test_format(s, "{}", 1.2345678)); + CHECK_EQUAL("1.5", test_format(s, "{}", 1.5)); + } + + //************************************************************************* + TEST(test_format_long_double) + { + etl::string<100> s; + + CHECK_EQUAL("1.0", test_format(s, "{}", 1.0l)); + auto& result = test_format(s, "{}", 1.234567l); + CHECK("1.234567" == result || "1.234566" == result); + CHECK_EQUAL("1.234567", test_format(s, "{}", 1.2345678l)); + CHECK_EQUAL("1.25", test_format(s, "{}", 1.25l)); + } + + //************************************************************************* + TEST(test_format_float_presentation) + { + etl::string<100> s; + + CHECK_EQUAL("1.000000e+00", test_format(s, "{:e}", 1.0f)); + CHECK_EQUAL("1.125000E+00", test_format(s, "{:E}", 1.125f)); + CHECK_EQUAL("-2.533324e-05", test_format(s, "{:e}", -0.00002533324f)); + CHECK_EQUAL("-2.500000e+11", test_format(s, "{:e}", -250000000000.0f)); + CHECK_EQUAL("1.000000", test_format(s, "{:f}", 1.0f)); + CHECK_EQUAL("1.125000", test_format(s, "{:F}", 1.125f)); + CHECK_EQUAL("1.000000", test_format(s, "{:g}", 1.0f)); + CHECK_EQUAL("1.125000", test_format(s, "{:G}", 1.125f)); + CHECK_EQUAL("1.000000e+10", test_format(s, "{:g}", 10000000000.0f)); + CHECK_EQUAL("1.000000E+10", test_format(s, "{:G}", 10000000000.0f)); + CHECK_EQUAL("nan", test_format(s, "{}", NAN)); + CHECK_EQUAL("nan", test_format(s, "{:e}", NAN)); + CHECK_EQUAL("NAN", test_format(s, "{:E}", NAN)); + CHECK_EQUAL("nan", test_format(s, "{:f}", NAN)); + CHECK_EQUAL("NAN", test_format(s, "{:F}", NAN)); + CHECK_EQUAL("nan", test_format(s, "{:g}", NAN)); + CHECK_EQUAL("NAN", test_format(s, "{:0.3G}", NAN)); + CHECK_EQUAL("inf", test_format(s, "{}", INFINITY)); + CHECK_EQUAL("inf", test_format(s, "{:e}", INFINITY)); + CHECK_EQUAL("INF", test_format(s, "{:E}", INFINITY)); + CHECK_EQUAL("inf", test_format(s, "{:f}", INFINITY)); + CHECK_EQUAL("INF", test_format(s, "{:F}", INFINITY)); + CHECK_EQUAL("inf", test_format(s, "{:g}", INFINITY)); + CHECK_EQUAL("INF", test_format(s, "{:0.3G}", INFINITY)); + CHECK_EQUAL("0x1.8p+0", test_format(s, "{:a}", 1.5f)); + CHECK_EQUAL("0X1.4CCCCCCCCCP+0", test_format(s, "{:A}", 1.3l)); + CHECK_EQUAL("0x2.49fp+4", test_format(s, "{:a}", 150000.0)); + CHECK_EQUAL("0x1.92a738p-5", test_format(s, "{:a}", 0.0000015f)); + CHECK_EQUAL("0x1.6345785d8ap+e", test_format(s, "{:a}", 100000000000000000.l)); + } + + //************************************************************************* + TEST(test_format_char_array) + { + etl::string<100> s; + + CHECK_EQUAL("s", test_format(s, "{}", "s")); + CHECK_EQUAL("abcd", test_format(s, "{}", "abcd")); + } + + //************************************************************************* + TEST(test_format_char) + { + etl::string<100> s; + + CHECK_EQUAL("a s b", test_format(s, "a {} b", 's')); + CHECK_EQUAL("a s b", test_format(s, "a {:c} b", 's')); + CHECK_EQUAL("a 's' b", test_format(s, "a {:?} b", 's')); + CHECK_EQUAL("a \t b", test_format(s, "a {} b", '\t')); + CHECK_EQUAL("a '\\t' b", test_format(s, "a {:?} b", '\t')); + CHECK_EQUAL("a '\\n' b", test_format(s, "a {:?} b", '\n')); + CHECK_EQUAL("a '\\r' b", test_format(s, "a {:?} b", '\r')); + CHECK_EQUAL("a '\\\"' b", test_format(s, "a {:?} b", '"')); + CHECK_EQUAL("a '\\'' b", test_format(s, "a {:?} b", '\'')); + CHECK_EQUAL("a '\\\\' b", test_format(s, "a {:?} b", '\\')); + CHECK_EQUAL("a '\\\\' b", test_format(s, "a {:?} b", '\\')); + CHECK_EQUAL("a 97 b", test_format(s, "a {:d} b", 'a')); + CHECK_EQUAL("a 61 b", test_format(s, "a {:X} b", 'a')); + CHECK_EQUAL("a 61 b", test_format(s, "a {:x} b", 'a')); + CHECK_EQUAL("a 0x61 b", test_format(s, "a {:#x} b", 'a')); + } + + //************************************************************************* + TEST(test_format_bool) + { + etl::string<100> s; + + CHECK_EQUAL("1false2true3", test_format(s, "1{}2{}3", false, true)); + CHECK_EQUAL("true", test_format(s, "{:s}", true)); + CHECK_EQUAL("1", test_format(s, "{:d}", true)); + CHECK_EQUAL("1", test_format(s, "{:X}", true)); + CHECK_EQUAL("01", test_format(s, "{:#o}", true)); + CHECK_EQUAL(" true", test_format(s, "{:10}", true)); + } + + //************************************************************************* + TEST(test_format_string_view) + { + etl::string<100> s; + etl::string_view sv("data1"); + + CHECK_EQUAL("data1", test_format(s, "{}", sv)); + CHECK_EQUAL("data1", test_format(s, "{:s}", sv)); + CHECK_THROW(test_format(s, "{:d}", sv), etl::bad_format_string_exception); + CHECK_EQUAL("data1 ", test_format(s, "{:10s}", sv)); + CHECK_EQUAL("data1 ", test_format(s, "{:<10s}", sv)); + CHECK_EQUAL(" data1", test_format(s, "{:>10s}", sv)); + CHECK_EQUAL(" data1 ", test_format(s, "{:^10s}", sv)); + CHECK_EQUAL("data1", test_format(s, "{:3}", sv)); + CHECK_EQUAL("dat", test_format(s, "{:.3s}", sv)); + CHECK_EQUAL("dat", test_format(s, "{:^.3s}", sv)); + CHECK_EQUAL(". dat !", test_format(s, ".{:^8.3s}!", sv)); + CHECK_EQUAL("^dat $", test_format(s, "^{:8.3s}$", sv)); + } + + //************************************************************************* + TEST(test_format_string_view_escaped) + { + etl::string<100> s; + etl::string_view sv("data1\n"); + + CHECK_EQUAL("\"data1\\n\"", test_format(s, "{:?}", sv)); + } + + //************************************************************************* + TEST(test_format_chars) + { + etl::string<100> s; + const char* chars = "data1"; + + CHECK_EQUAL("data1", test_format(s, "{}", chars)); + CHECK_EQUAL("data1", test_format(s, "{:s}", chars)); + CHECK_THROW(test_format(s, "{:d}", chars), etl::bad_format_string_exception); + CHECK_EQUAL("data1 ", test_format(s, "{:10s}", chars)); + CHECK_EQUAL("data1 ", test_format(s, "{:<10s}", chars)); + CHECK_EQUAL(" data1", test_format(s, "{:>10s}", chars)); + CHECK_EQUAL(" data1 ", test_format(s, "{:^10s}", chars)); + CHECK_EQUAL("data1", test_format(s, "{:3}", chars)); + CHECK_EQUAL("dat", test_format(s, "{:.3s}", chars)); + CHECK_EQUAL("dat", test_format(s, "{:^.3s}", chars)); + CHECK_EQUAL(". dat !", test_format(s, ".{:^8.3s}!", chars)); + CHECK_EQUAL("^dat $", test_format(s, "^{:8.3s}$", chars)); + } + + //************************************************************************* + TEST(test_format_chars_escaped) + { + etl::string<100> s; + const char* chars = "data2\n"; + + CHECK_EQUAL("\"data2\\n\"", test_format(s, "{:?}", chars)); + } + + //************************************************************************* + TEST(test_format_pointer) + { + etl::string<100> s; + + void* p = nullptr; + + CHECK_EQUAL("0x0", test_format(s, "{}", p)); + CHECK_EQUAL("0x0", test_format(s, "{:p}", p)); + CHECK_EQUAL("0X0", test_format(s, "{:P}", p)); + + if (sizeof(uintptr_t) == 8) + { + p = reinterpret_cast(0x123456789abcdef0ULL); + CHECK_EQUAL("0x123456789abcdef0", test_format(s, "{:p}", p)); + CHECK_EQUAL("0X123456789ABCDEF0", test_format(s, "{:P}", p)); + } + else if (sizeof(uintptr_t) == 4) + { + p = reinterpret_cast(0x1abcdef0ULL); + CHECK_EQUAL("0x1abcdef0", test_format(s, "{:p}", p)); + CHECK_EQUAL("0X1ABCDEF0", test_format(s, "{:P}", p)); + } + } + + //************************************************************************* + TEST(test_format_limit) + { + etl::string<10> s; + + CHECK_EQUAL("abcdefghij", test_format(s, "abcdefghijkl")); + CHECK_EQUAL("abcdefgh12", test_format(s, "abcdefgh{}", 123)); + } + + //************************************************************************* + TEST(test_format_escape) + { + etl::string<100> s; + + CHECK_EQUAL("abc{def", test_format(s, "abc{{def")); + CHECK_EQUAL("}abc", test_format(s, "}}abc")); + } + + //************************************************************************* + TEST(test_format_invalid) + { + etl::string<100> s; + + CHECK_THROW(test_format(s, "a{b}", 1), etl::bad_format_string_exception); // bad format index spec + // goal: rejected at compile time on C++20, error on <= C++17 + + CHECK_THROW(test_format(s, "a{b"), etl::bad_format_string_exception); // closing brace missing + // goal: rejected at compile time on C++20, error on <= C++17 + + CHECK_THROW(test_format(s, "a{b}"), etl::bad_format_string_exception); // arg missing + // goal: rejected at compile time on C++20, error on <= C++17 + + CHECK_THROW(test_format(s, "a}b"), etl::bad_format_string_exception); // bad format: only escaped }} allowed + // goal: rejected at compile time on C++20, error on <= C++17 + + CHECK_EQUAL("123", test_format(s, "{:}", 123)); // valid + CHECK_THROW(test_format(s, "{::}", 123), etl::bad_format_string_exception); // bad format spec + CHECK_THROW(test_format(s, "{1}", 123), etl::bad_format_string_exception); // bad index + } + + //************************************************************************* + TEST(test_format_indexed) + { + etl::string<100> s; + + CHECK_EQUAL("34 56", test_format(s, "{0} {1}", 34, 56)); + CHECK_EQUAL("56 34", test_format(s, "{1} {0}", 34, 56)); + CHECK_EQUAL("134 134", test_format(s, "{0} {0}", 134)); + CHECK_EQUAL("a b c d e f g h i j k l m n", test_format(s, "{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13}", 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n')); + } + + //************************************************************************* + TEST(test_formatted_size) + { + CHECK_EQUAL(0, etl::formatted_size("")); + CHECK_EQUAL(0, etl::formatted_size("{}", "")); + CHECK_EQUAL(5, etl::formatted_size("xyz{}", 12)); + CHECK_EQUAL(3, etl::formatted_size("{}", "abc")); + } + + //************************************************************************* + TEST(test_iterator) + { + etl::string<100> s; + + etl::istring::iterator result = etl::format_to(s.begin(), "{0} {1}", 34, 56); + s.uninitialized_resize(result - s.begin()); + CHECK_EQUAL("34 56", s); + + s.clear(); + etl::format_to(iterator(s), ""); + CHECK_EQUAL("", s); + + s.clear(); + etl::format_to(iterator(s), "{0} {1}", 65, 34); + CHECK_EQUAL("65 34", s); + + s = "abcdefghij"; + etl::format_to_n(s.begin(), 3, "xy{}", 123); + CHECK_EQUAL("xy1defghij", s); + } + + //************************************************************************* + TEST(test_format_spec_fill_and_align) + { + etl::string<100> s; + + CHECK_EQUAL("a 34", test_format(s, "a{:5}", 34)); + CHECK_EQUAL("a34", test_format(s, "a{:1}", 34)); + CHECK_EQUAL("a34", test_format(s, "a{:2}", 34)); + CHECK_EQUAL(" 34", test_format(s, "{:>4}", 34)); + CHECK_EQUAL(" 34 ", test_format(s, "{:^4}", 34)); + CHECK_EQUAL(" 34 ", test_format(s, "{:^5}", 34)); + CHECK_EQUAL(" -65 ", test_format(s, "{:^5}", -65)); + CHECK_EQUAL("34 ", test_format(s, "{:<4}", 34)); + CHECK_THROW(test_format(s, "a{:*5}", 34), etl::bad_format_string_exception); + CHECK_EQUAL("a*34**", test_format(s, "a{:*^5}", 34)); + CHECK_EQUAL("a*34**", test_format(s, "a{:*^5}", static_cast(34))); + CHECK_EQUAL("a***-341234567890****", test_format(s, "a{:*^20}", static_cast(-341234567890))); + CHECK_EQUAL("a****341234567890****", test_format(s, "a{:*^20}", static_cast(341234567890))); + CHECK_EQUAL(" x ", test_format(s, "{: ^20}", 'x')); + CHECK_EQUAL("x ", test_format(s, "{:20}", 'x')); + CHECK_EQUAL(" x", test_format(s, "{:>20}", 'x')); + CHECK_EQUAL("x ", test_format(s, "{:<20}", 'x')); + } + + //************************************************************************* + TEST(test_format_spec_sign) + { + etl::string<100> s; + + CHECK_EQUAL("34", test_format(s, "{:-}", 34)); + CHECK_EQUAL("-34", test_format(s, "{:-}", -34)); + CHECK_EQUAL("-34", test_format(s, "{:+}", -34)); + CHECK_EQUAL("+34", test_format(s, "{:+}", 34)); + CHECK_EQUAL("+0", test_format(s, "{:+}", 0)); + CHECK_EQUAL("0", test_format(s, "{:-}", 0)); + CHECK_EQUAL("210", test_format(s, "{:-}", 210)); + CHECK_EQUAL("-210", test_format(s, "{:-}", -210)); + CHECK_EQUAL(" 0", test_format(s, "{: }", 0)); + CHECK_EQUAL(" 546", test_format(s, "{: }", 546)); + CHECK_EQUAL("-546", test_format(s, "{: }", -546)); + } + + //************************************************************************* + TEST(test_format_int_presentation) + { + etl::string<100> s; + + CHECK_EQUAL("134", test_format(s, "{:d}", 134)); + CHECK_EQUAL("3f4", test_format(s, "{:x}", 0x3f4)); + CHECK_EQUAL("123456789abcdef0", test_format(s, "{:x}", 0x123456789abcdef0ULL)); + CHECK_EQUAL("0x3f4", test_format(s, "{:#x}", 0x3f4)); + CHECK_EQUAL("3F4", test_format(s, "{:X}", 0x3f4)); + CHECK_EQUAL("0X3F4", test_format(s, "{:#X}", 0x3f4)); + CHECK_EQUAL("34", test_format(s, "{:o}", 034)); + CHECK_EQUAL("034", test_format(s, "{:#o}", 034)); + CHECK_EQUAL("1010", test_format(s, "{:b}", 0b1010)); + CHECK_EQUAL("0b1010", test_format(s, "{:#b}", 0b1010)); + CHECK_EQUAL("1010", test_format(s, "{:B}", 0b1010)); + CHECK_EQUAL("0B1010", test_format(s, "{:#B}", 0b1010)); + CHECK_EQUAL("-0B1010", test_format(s, "{:#B}", -0b1010)); + CHECK_EQUAL("C", test_format(s, "{:c}", 67)); + CHECK_EQUAL("00067", test_format(s, "{:05d}", 67)); + CHECK_EQUAL("+00067", test_format(s, "{:+05d}", 67)); + CHECK_EQUAL("+0X00EF1", test_format(s, "{:+#05X}", 0xEF1)); + CHECK_THROW(test_format(s, "{:+#05.5X}", 0xEF1), etl::bad_format_string_exception); + } + } +} + +#endif \ No newline at end of file diff --git a/test/test_print.cpp b/test/test_print.cpp new file mode 100644 index 00000000..08573975 --- /dev/null +++ b/test/test_print.cpp @@ -0,0 +1,82 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 BMW AG + +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. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/print.h" + +#if ETL_USING_CPP11 + +namespace { + using iterator = etl::back_insert_iterator; + + etl::string<100> output; +} + +// to be implemented in a concrete project, typically printing to a serial console +extern "C" void etl_putchar(int c) +{ + iterator output_it(output); + *output_it = static_cast(c); +} + +namespace +{ + SUITE(test_format) + { + //************************************************************************* + TEST(test_print) + { + output.clear(); + etl::print("Hello Print!"); + CHECK_EQUAL("Hello Print!", output); + + output.clear(); + etl::print("{}", "Hello format!"); + CHECK_EQUAL("Hello format!", output); + + output.clear(); + etl::print("Hello {}!", 321); + CHECK_EQUAL("Hello 321!", output); + } + + //************************************************************************* + TEST(test_println) + { + output.clear(); + etl::println("Hello Println!"); + CHECK_EQUAL("Hello Println!\n", output); + + output.clear(); + etl::println(); + CHECK_EQUAL("\n", output); + } + } +} + +#endif \ No newline at end of file