diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -108,6 +108,7 @@ __format/format_parse_context.h __format/format_string.h __format/formatter.h + __format/formatter_string.h __format/parser_std_format_spec.h __function_like.h __functional/search.h diff --git a/libcxx/include/__format/formatter.h b/libcxx/include/__format/formatter.h --- a/libcxx/include/__format/formatter.h +++ b/libcxx/include/__format/formatter.h @@ -13,7 +13,11 @@ #include <__availability> #include <__config> #include <__format/format_error.h> +#include <__format/format_fwd.h> +#include <__format/format_string.h> #include <__format/parser_std_format_spec.h> +#include +#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) #pragma GCC system_header @@ -55,6 +59,131 @@ } }; +namespace __formatter { + +struct _LIBCPP_TEMPLATE_VIS __padding_size_result { + size_t __before; + size_t __after; +}; + +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY constexpr __padding_size_result +__padding_size(size_t __size, size_t __width, + __format_spec::_Flags::_Alignment __align) { + _LIBCPP_ASSERT(__width > __size, + "Don't call this function when no padding is required"); + _LIBCPP_ASSERT( + __align != __format_spec::_Flags::_Alignment::__default, + "Caller should adjust the default to the value required by the type"); + + size_t __fill = __width - __size; + switch (__align) { + case __format_spec::_Flags::_Alignment::__default: + _LIBCPP_UNREACHABLE(); + + case __format_spec::_Flags::_Alignment::__left: + return {0, __fill}; + + case __format_spec::_Flags::_Alignment::__center: { + // The extra padding is divided per [format.string.std]/3 + // __before = floor(__fill, 2); + // __after = ceil(__fill, 2); + size_t __before = __fill / 2; + size_t __after = __fill - __before; + return {__before, __after}; + } + case __format_spec::_Flags::_Alignment::__right: + return {__fill, 0}; + } + _LIBCPP_UNREACHABLE(); +} + +/** + * Writes the input to the output with the required padding. + * + * Since the output column width is specified the function can be used for + * ASCII and Unicode input. + * + * @pre [@a __first, @a __last) is a valid range. + * @pre @a __size <= @a __width. Using this function when this pre-condition + * doesn't hold incurs an unwanted overhead. + * + * @param __out_it The output iterator to write to. + * @param __first Pointer to the first element to write. + * @param __last Pointer beyond the last element to write. + * @param __size The (estimated) output column width. When the elements + * to be written are ASCII the following condition holds + * @a __size == @a __last - @a __first. + * @param __width The number of output columns to write. + * @param __fill The character used for the alignment of the output. + * TODO FMT Will probably change to support Unicode grapheme + * cluster. + * @param __alignment The requested alignment. + * + * @returns An iterator pointing beyond the last element written. + * + * @note The type of the input @a __first, @a __last can differ from the type + * of @a __fill. Integer output uses @c std::to_chars for its conversion. This + * function returns a buffer with @c char elements. + */ +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY auto +__write(__output_iterator auto __out_it, const _CharT* __first, + const _CharT* __last, size_t __size, size_t __width, _Fill __fill, + __format_spec::_Flags::_Alignment __alignment) -> decltype(__out_it) { + + _LIBCPP_ASSERT(__first <= __last, "Not a valid range"); + _LIBCPP_ASSERT(__size < __width, "Precondition failure"); + + __padding_size_result __padding = + __padding_size(__size, __width, __alignment); + __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before, __fill); + __out_it = _VSTD::copy(__first, __last, _VSTD::move(__out_it)); + return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill); +} + +/** + * Writes Unicode input to the output with the required padding. + * + * This function does almost the same as the @ref __write function, but handles + * the width estimation of the Unicode input. + * + * @param __str The range [@a __first, @a __last). + * @param __precision The width to truncate the input string to, use @c -1 for + * no limit. + */ +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY auto +__write_unicode(__output_iterator auto __out_it, + basic_string_view<_CharT> __str, ptrdiff_t __width, + ptrdiff_t __precision, _Fill __fill, + __format_spec::_Flags::_Alignment __alignment) + -> decltype(__out_it) { + + // This value changes when there Unicode column width limits the output + // size. + auto __last = __str.end(); + if (__width != 0 || __precision != -1) { + __format_spec::__format_string_traits<_CharT> __format_traits = + __format_spec::__get_format_string_traits(__str.begin(), __str.end(), + __width, __precision); + + if (__format_traits.__align) + return __write(_VSTD::move(__out_it), __str.begin(), + __format_traits.__last, __format_traits.__size, __width, + __fill, __alignment); + + // No alignment required update the output based on the precision. + // This might be the same as __str.end(). + __last = __format_traits.__last; + } + + // Copy the input to the output. The output size might be limited by the + // precision. + return _VSTD::copy(__str.begin(), __last, _VSTD::move(__out_it)); +} + +} // namespace __formatter + #endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) #endif //_LIBCPP_STD_VER > 17 diff --git a/libcxx/include/__format/formatter_string.h b/libcxx/include/__format/formatter_string.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/formatter_string.h @@ -0,0 +1,184 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FORMAT_FORMATTER_STRING_H +#define _LIBCPP___FORMAT_FORMATTER_STRING_H + +#include <__config> +#include <__format/format_error.h> +#include <__format/format_fwd.h> +#include <__format/format_string.h> +#include <__format/formatter.h> +#include <__format/parser_std_format_spec.h> +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && \ + !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +namespace __format_spec { + +template +class _LIBCPP_TEMPLATE_VIS __formatter_string + : public __parser_std<_CharT, __parser_precision, __parser_no_sign, + __parser_no_alternate_form, __parser_no_zero_padding, + __parser_no_locale_specific_form> { +public: + using _Base = + __parser_std<_CharT, __parser_precision, __parser_no_sign, + __parser_no_alternate_form, __parser_no_zero_padding, + __parser_no_locale_specific_form>; + + _LIBCPP_INLINE_VISIBILITY constexpr __formatter_string() { + this->__alignment = _Flags::_Alignment::__left; + } + + _LIBCPP_INLINE_VISIBILITY constexpr auto parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + + auto __it = _Base::parse(__parse_ctx); + switch (this->__type) { + case _Flags::_Type::__default: + case _Flags::_Type::__string: + break; + + default: + __throw_format_error("The format-spec type has a type not supported for " + "a string argument"); + } + + return __it; + } + + _LIBCPP_INLINE_VISIBILITY auto format(basic_string_view<_CharT> __str, + auto& __ctx) -> decltype(__ctx.out()) { + + _LIBCPP_ASSERT(this->__alignment != _Flags::_Alignment::__default, + "The parser should not use these defaults"); + + if (this->__width_needs_substitution()) + this->__substitute_width_arg_id(__ctx.arg(this->__width)); + + if (this->__precision_needs_substitution()) + this->__substitute_precision_arg_id(__ctx.arg(this->__precision)); + + return __formatter::__write_unicode( + __ctx.out(), __str, this->__width, + this->__has_precision_field() ? this->__precision : -1, this->__fill, + this->__alignment); + } +}; + +} //namespace __format_spec + +// [format.formatter.spec]/2.2 For each charT, the string type specializations + +// Formatter const char*. +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_string<_CharT> { + using _Base = __format_spec::__formatter_string<_CharT>; + + _LIBCPP_INLINE_VISIBILITY auto format(const _CharT* __str, auto& __ctx) + -> decltype(__ctx.out()) { + _LIBCPP_ASSERT(__str, "The basic_format_arg constructor should have " + "prevented an invalid pointer."); + + // When using a center or right alignment and the width option the length + // of __str must be known to add the padding upfront. This case is handled + // by the base class by converting the argument to a basic_string_view. + // + // When using left alignment and the width option the padding is added + // after outputting __str so the length can be determined while outputting + // __str. The same holds true for the precision, during outputting __str it + // can be validated whether the precision threshold has been reached. For + // now these optimizations aren't implemented. Instead the base class + // handles these options. + // TODO FMT Implement these improvements. + if (this->__has_width_field() || this->__has_precision_field()) + return _Base::format(__str, __ctx); + + // No formatting required, copy the string to the output. + auto __out_it = __ctx.out(); + while (*__str) + *__out_it++ = *__str++; + return __out_it; + } +}; + +// Formatter char*. +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter<_CharT*, _CharT> : public formatter { + using _Base = formatter; + + _LIBCPP_INLINE_VISIBILITY auto format(_CharT* __str, auto& __ctx) + -> decltype(__ctx.out()) { + return _Base::format(__str, __ctx); + } +}; + +// Formatter const char[]. +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_string<_CharT> { + using _Base = __format_spec::__formatter_string<_CharT>; + + _LIBCPP_INLINE_VISIBILITY auto format(const _CharT __str[_Size], auto& __ctx) + -> decltype(__ctx.out()) { + return _Base::format(_VSTD::basic_string_view<_CharT>(__str, _Size), __ctx); + } +}; + +// Formatter std::string. +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter, _CharT> + : public __format_spec::__formatter_string<_CharT> { + using _Base = __format_spec::__formatter_string<_CharT>; + + _LIBCPP_INLINE_VISIBILITY auto + format(const basic_string<_CharT, _Traits, _Allocator>& __str, auto& __ctx) + -> decltype(__ctx.out()) { + return _Base::format(_VSTD::basic_string_view<_CharT>(__str), __ctx); + } +}; + +// Formatter std::string_view. +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter, _CharT> + : public __format_spec::__formatter_string<_CharT> {}; + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_FORMATTER_STRING_H diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h --- a/libcxx/include/__format/parser_std_format_spec.h +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -285,6 +285,7 @@ static_cast<_CT>(__format::__number_max)) __throw_format_error("A format-spec arg-id replacement exceeds " "the maximum supported value"); + return __arg; } else if constexpr (same_as<_Type, monostate>) __throw_format_error("Argument index out of bounds"); diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -281,8 +281,9 @@ #include <__format/format_error.h> #include <__format/format_parse_context.h> #include <__format/format_string.h> -#include <__format/parser_std_format_spec.h> #include <__format/formatter.h> +#include <__format/formatter_string.h> +#include <__format/parser_std_format_spec.h> #include #include #include @@ -359,43 +360,6 @@ } }; -template -struct _LIBCPP_TEMPLATE_VIS __formatter_c_string { - _LIBCPP_INLINE_VISIBILITY - auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { - // TODO FMT Implement this function. - return __parse_ctx.begin(); - } - - _LIBCPP_INLINE_VISIBILITY - auto format(const _CharT* __str, auto& __ctx) -> decltype(__ctx.out()) { - // TODO FMT Implement the parsed formatting arguments. - auto __out_it = __ctx.out(); - while (*__str) - *__out_it++ = *__str++; - return __out_it; - } -}; - -template -struct _LIBCPP_TEMPLATE_VIS __formatter_string { - _LIBCPP_INLINE_VISIBILITY - auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { - // TODO FMT Implement this function. - return __parse_ctx.begin(); - } - - _LIBCPP_INLINE_VISIBILITY - auto format(basic_string_view<_CharT> __str, auto& __ctx) - -> decltype(__ctx.out()) { - // TODO FMT Implement the parsed formatting arguments. - auto __out_it = __ctx.out(); - for (const auto __c : __str) - *__out_it++ = __c; - return __out_it; - } -}; - template requires(is_arithmetic_v<_Tp> && !same_as<_Tp, bool>) struct _LIBCPP_INLINE_VISIBILITY @@ -475,54 +439,6 @@ struct _LIBCPP_TEMPLATE_VIS formatter : public __format::__formatter_char {}; -// [format.formatter.spec]/2.2 For each charT, the string type specializations - -template -struct _LIBCPP_TEMPLATE_VIS formatter<_CharT*, _CharT> - : public __format::__formatter_c_string<_CharT> { - using _Base = __format::__formatter_c_string<_CharT>; - - _LIBCPP_INLINE_VISIBILITY auto format(_CharT* __str, auto& __ctx) - -> decltype(__ctx.out()) { - _LIBCPP_ASSERT(__str, "The basic_format_arg constructor should have " - "prevented an invalid pointer"); - return _Base::format(__str, __ctx); - } -}; - -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_c_string<_CharT> {}; - -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_string<_CharT> { - using _Base = __format::__formatter_string<_CharT>; - - _LIBCPP_INLINE_VISIBILITY auto format(const _CharT __str[_Size], auto& __ctx) - -> decltype(__ctx.out()) { - return _Base::format(_VSTD::basic_string_view<_CharT>(__str, _Size), __ctx); - } -}; - -template -struct _LIBCPP_TEMPLATE_VIS - formatter, _CharT> - : public __format::__formatter_string<_CharT> { - using _Base = __format::__formatter_string<_CharT>; - - _LIBCPP_INLINE_VISIBILITY auto - format(const basic_string<_CharT, _Traits, _Allocator>& __str, auto& __ctx) - -> decltype(__ctx.out()) { - return _Base::format(_VSTD::basic_string_view<_CharT>(__str), __ctx); - } -}; - -template -struct _LIBCPP_TEMPLATE_VIS - formatter, _CharT> - : public __format::__formatter_string<_CharT> {}; - // [format.formatter.spec]/2.3 // For each charT, for each cv-unqualified arithmetic type ArithmeticT other // than char, wchar_t, char8_t, char16_t, or char32_t, a specialization diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -403,6 +403,7 @@ module format_parse_context { header "__format/format_parse_context.h" } module format_string { header "__format/format_string.h" } module formatter { header "__format/formatter.h" } + module formatter_string { header "__format/formatter_string.h" } module parser_std_format_spec { header "__format/parser_std_format_spec.h" } } } diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.c_string.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.c_string.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.c_string.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.c_string.pass.cpp @@ -32,7 +32,9 @@ void test(StringT expected, StringT fmt, const CharT* a) { auto parse_ctx = std::basic_format_parse_context(fmt); std::formatter formatter; - formatter.parse(parse_ctx); + auto it = formatter.parse(parse_ctx); + assert(it != parse_ctx.end()); + assert(*it == CharT('}')); StringT result; auto out = std::back_inserter(result); @@ -75,6 +77,20 @@ test(STR("abc"), STR("}"), s); } else assert(false && "Implement tests for the missing types"); + + test(STR("world"), STR("}"), CSTR("world")); + test(STR("world"), STR("_>}"), CSTR("world")); + + test(STR(" world"), STR(">8}"), CSTR("world")); + test(STR("___world"), STR("_>8}"), CSTR("world")); + test(STR("_world__"), STR("_^8}"), CSTR("world")); + test(STR("world___"), STR("_<8}"), CSTR("world")); + + test(STR("world"), STR(".5}"), CSTR("world")); + test(STR("unive"), STR(".5}"), CSTR("universe")); + + test(STR("%world%"), STR("%^7.7}"), CSTR("world")); + test(STR("univers"), STR("%^7.7}"), CSTR("universe")); } template diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.const_char_array.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.const_char_array.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.const_char_array.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.const_char_array.pass.cpp @@ -46,7 +46,9 @@ using Str = const CharT[size]; std::basic_format_parse_context parse_ctx{fmt}; std::formatter formatter; - formatter.parse(parse_ctx); + auto it = formatter.parse(parse_ctx); + assert(it != parse_ctx.end()); + assert(*it == CharT('}')); std::basic_string result; using Ctx = std::basic_format_context< @@ -92,6 +94,20 @@ test<"abc\0abc">(s, std::wstring(L"}")); } else assert(false && "Implement tests for the missing types"); + + test<"world">(STR("world"), STR("}")); + test<"world">(STR("world"), STR("_>}")); + + test<"world">(STR(" world"), STR(">8}")); + test<"world">(STR("___world"), STR("_>8}")); + test<"world">(STR("_world__"), STR("_^8}")); + test<"world">(STR("world___"), STR("_<8}")); + + test<"world">(STR("world"), STR(".5}")); + test<"universe">(STR("unive"), STR(".5}")); + + test<"world">(STR("%world%"), STR("%^7.7}")); + test<"universe">(STR("univers"), STR("%^7.7}")); } int main(int, char**) { diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.string.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.string.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.string.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.string.pass.cpp @@ -39,7 +39,9 @@ auto parse_ctx = std::basic_format_parse_context(fmt); std::formatter formatter; - formatter.parse(parse_ctx); + auto it = formatter.parse(parse_ctx); + assert(it != parse_ctx.end()); + assert(*it == CharT('}')); StringT result; auto out = std::back_inserter(result); @@ -79,6 +81,20 @@ test(s, STR("}"), s); } else assert(false && "Implement tests for the missing types"); + + test(STR("world"), STR("}"), STR("world")); + test(STR("world"), STR("_>}"), STR("world")); + + test(STR(" world"), STR(">8}"), STR("world")); + test(STR("___world"), STR("_>8}"), STR("world")); + test(STR("_world__"), STR("_^8}"), STR("world")); + test(STR("world___"), STR("_<8}"), STR("world")); + + test(STR("world"), STR(".5}"), STR("world")); + test(STR("unive"), STR(".5}"), STR("universe")); + + test(STR("%world%"), STR("%^7.7}"), STR("world")); + test(STR("univers"), STR("%^7.7}"), STR("universe")); } template diff --git a/libcxx/test/std/utilities/format/format.functions/format.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format.pass.cpp --- a/libcxx/test/std/utilities/format/format.functions/format.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/format.pass.cpp @@ -25,6 +25,7 @@ #ifndef _LIBCPP_HAS_NO_LOCALIZATION #include #endif +#include #include "test_macros.h" #include "make_string.h" diff --git a/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp b/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp --- a/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "test_macros.h" #include "make_string.h" diff --git a/libcxx/test/std/utilities/format/format.functions/tests.inc b/libcxx/test/std/utilities/format/format.functions/tests.inc --- a/libcxx/test/std/utilities/format/format.functions/tests.inc +++ b/libcxx/test/std/utilities/format/format.functions/tests.inc @@ -9,11 +9,244 @@ #define STR(S) MAKE_STRING(CharT, S) +template +std::vector> invalid_types(std::string valid) { + std::vector> result; + +#define CASE(T) \ + case #T[0]: \ + result.push_back(STR("Invalid formatter type {:" #T "}")); \ + break; + + for (auto type : "aAbBcdeEfFgGopsxX") { + if (valid.find(type) != std::string::npos) + continue; + + switch (type) { + CASE(a) + CASE(A) + CASE(b) + CASE(B) + CASE(c) + CASE(d) + CASE(e) + CASE(E) + CASE(f) + CASE(F) + CASE(g) + CASE(G) + CASE(o) + CASE(p) + CASE(s) + CASE(x) + CASE(X) + case 0: + break; + default: + assert(false && "Add the type to the list of cases."); + } + } +#undef CASE + + return result; +} + void test_char_to_wchar() { using CharT = wchar_t; test(STR("hello 09azA"), STR("hello {}{}{}{}{}"), '0', '9', 'a', 'z', 'A'); } +template +void test_string(T world, T universe) { + + // *** Valid input tests *** + // Unsed argument is ignored. TODO FMT what does the Standard mandate? + test(STR("hello world"), STR("hello {}"), world, universe); + test(STR("hello world and universe"), STR("hello {} and {}"), world, + universe); + test(STR("hello world"), STR("hello {0}"), world, universe); + test(STR("hello universe"), STR("hello {1}"), world, universe); + test(STR("hello universe and world"), STR("hello {1} and {0}"), world, + universe); + + test(STR("hello world"), STR("hello {:_>}"), world); + test(STR("hello world"), STR("hello {:>8}"), world); + test(STR("hello ___world"), STR("hello {:_>8}"), world); + test(STR("hello _world__"), STR("hello {:_^8}"), world); + test(STR("hello world___"), STR("hello {:_<8}"), world); + + test(STR("hello >>>world"), STR("hello {:>>8}"), world); + test(STR("hello <<8}"), world); + test(STR("hello ^^^world"), STR("hello {:^>8}"), world); + + test(STR("hello $world"), STR("hello {:$>{}}"), world, 6); + test(STR("hello $world"), STR("hello {0:$>{1}}"), world, 6); + test(STR("hello $world"), STR("hello {1:$>{0}}"), 6, world); + + test(STR("hello world"), STR("hello {:.5}"), world); + test(STR("hello unive"), STR("hello {:.5}"), universe); + + test(STR("hello univer"), STR("hello {:.{}}"), universe, 6); + test(STR("hello univer"), STR("hello {0:.{1}}"), universe, 6); + test(STR("hello univer"), STR("hello {1:.{0}}"), 6, universe); + + test(STR("hello %world%"), STR("hello {:%^7.7}"), world); + test(STR("hello univers"), STR("hello {:%^7.7}"), universe); + test(STR("hello %world%"), STR("hello {:%^{}.{}}"), world, 7, 7); + test(STR("hello %world%"), STR("hello {0:%^{1}.{2}}"), world, 7, 7); + test(STR("hello %world%"), STR("hello {0:%^{2}.{1}}"), world, 7, 7); + test(STR("hello %world%"), STR("hello {1:%^{0}.{2}}"), 7, world, 7); + + test(STR("hello world"), STR("hello {:_>s}"), world); + test(STR("hello $world"), STR("hello {:$>{}s}"), world, 6); + test(STR("hello world"), STR("hello {:.5s}"), world); + test(STR("hello univer"), STR("hello {:.{}s}"), universe, 6); + test(STR("hello %world%"), STR("hello {:%^7.7s}"), world); + + test(STR("hello #####uni"), STR("hello {:#>8.3s}"), universe); + test(STR("hello ##uni###"), STR("hello {:#^8.3s}"), universe); + test(STR("hello uni#####"), STR("hello {:#<8.3s}"), universe); + + // *** sign *** + test_exception("A sign field isn't allowed in this format-spec", + STR("hello {:-}"), world); + + // *** alternate form *** + test_exception("An alternate form field isn't allowed in this format-spec", + STR("hello {:#}"), world); + + // *** zero-padding *** + test_exception("A zero-padding field isn't allowed in this format-spec", + STR("hello {:0}"), world); + + // *** width *** + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test_exception("The numeric value of the format-spec is too large", + STR("{:2147483648}"), world); + test_exception("The numeric value of the format-spec is too large", + STR("{:5000000000}"), world); + test_exception("The numeric value of the format-spec is too large", + STR("{:10000000000}"), world); + + test_exception( + "A format-spec width field replacement should have a positive value", + STR("hello {:{}}"), world, 0); + test_exception( + "A format-spec arg-id replacement shouldn't have a negative value", + STR("hello {:{}}"), world, -1); + test_exception( + "A format-spec arg-id replacement exceeds the maximum supported value", + STR("hello {:{}}"), world, -1u); + test_exception("Argument index out of bounds", STR("hello {:{}}"), world); + test_exception( + "A format-spec arg-id replacement argument isn't an integral type", + STR("hello {:{}}"), world, universe); + test_exception( + "Using manual argument numbering in automatic argument numbering mode", + STR("hello {:{0}}"), world, 1); + test_exception( + "Using automatic argument numbering in manual argument numbering mode", + STR("hello {0:{}}"), world, 1); + + // *** precision *** + test_exception("A format-spec precision field shouldn't have a leading zero", + STR("hello {:.01}"), world); + + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test_exception("The numeric value of the format-spec is too large", + STR("{:.2147483648}"), world); + test_exception("The numeric value of the format-spec is too large", + STR("{:.5000000000}"), world); + test_exception("The numeric value of the format-spec is too large", + STR("{:.10000000000}"), world); + + // Precision 0 allowed, but not useful for string arguments. + test(STR("hello "), STR("hello {:.{}}"), world, 0); + test_exception( + "A format-spec arg-id replacement shouldn't have a negative value", + STR("hello {:.{}}"), world, -1); + test_exception( + "A format-spec arg-id replacement exceeds the maximum supported value", + STR("hello {:.{}}"), world, -1u); + test_exception("Argument index out of bounds", STR("hello {:.{}}"), world); + test_exception( + "A format-spec arg-id replacement argument isn't an integral type", + STR("hello {:.{}}"), world, universe); + test_exception( + "Using manual argument numbering in automatic argument numbering mode", + STR("hello {:.{0}}"), world, 1); + test_exception( + "Using automatic argument numbering in manual argument numbering mode", + STR("hello {0:.{}}"), world, 1); + + // *** locale-specific form *** + test_exception( + "A locale-specific form field isn't allowed in this format-spec", + STR("hello {:L}"), world); + + // *** type *** + for (const auto& fmt : invalid_types("s")) + test_exception( + "The format-spec type has a type not supported for a string argument", + fmt, world); +} + +template +void test_string_unicode() { +#ifndef _LIBCPP_HAS_NO_UNICODE + // ß requires one column + test(STR("aßc"), STR("{}"), STR("aßc")); + + test(STR("aßc"), STR("{:.3}"), STR("aßc")); + test(STR("aß"), STR("{:.2}"), STR("aßc")); + test(STR("a"), STR("{:.1}"), STR("aßc")); + + test(STR("aßc"), STR("{:3.3}"), STR("aßc")); + test(STR("aß"), STR("{:2.2}"), STR("aßc")); + test(STR("a"), STR("{:1.1}"), STR("aßc")); + + test(STR("aßc---"), STR("{:-<6}"), STR("aßc")); + test(STR("-aßc--"), STR("{:-^6}"), STR("aßc")); + test(STR("---aßc"), STR("{:->6}"), STR("aßc")); + + // \u1000 requires two columns + test(STR("a\u1110c"), STR("{}"), STR("a\u1110c")); + + test(STR("a\u1100c"), STR("{:.4}"), STR("a\u1100c")); + test(STR("a\u1100"), STR("{:.3}"), STR("a\u1100c")); + test(STR("a"), STR("{:.2}"), STR("a\u1100c")); + test(STR("a"), STR("{:.1}"), STR("a\u1100c")); + + test(STR("a\u1100c"), STR("{:-<4.4}"), STR("a\u1100c")); + test(STR("a\u1100"), STR("{:-<3.3}"), STR("a\u1100c")); + test(STR("a-"), STR("{:-<2.2}"), STR("a\u1100c")); + test(STR("a"), STR("{:-<1.1}"), STR("a\u1100c")); + + test(STR("a\u1110c---"), STR("{:-<7}"), STR("a\u1110c")); + test(STR("-a\u1110c--"), STR("{:-^7}"), STR("a\u1110c")); + test(STR("---a\u1110c"), STR("{:->7}"), STR("a\u1110c")); +#endif +} + +template +void test_string() { + std::basic_string world = STR("world"); + std::basic_string universe = STR("universe"); + + // Testing the char const[] is a bit tricky due to array to pointer decay. + // Since there are separate tests in format.formatter.spec the array is not + // tested here. + test_string(world.c_str(), universe.c_str()); + test_string(const_cast(world.c_str()), + const_cast(universe.c_str())); + test_string(std::basic_string_view(world), + std::basic_string_view(universe)); + test_string(world, universe); + test_string_unicode(); +} + template void test() { // *** Test escaping *** @@ -75,6 +308,7 @@ std::basic_string_view data = buffer; test(STR("hello world"), STR("hello {}"), data); } + test_string(); // *** Test Boolean format argument *** test(STR("hello 01"), STR("hello {}{}"), false, true); diff --git a/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp b/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp --- a/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "test_macros.h" #include "make_string.h" diff --git a/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp @@ -0,0 +1,203 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts + +// + +// Tests the parsing of the format string as specified in [format.string.std]. +// It validates whether the std-format-spec is valid for a string type. + +#include +#include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +#include +#endif + +#include "test_macros.h" +#include "make_string.h" + +#define CSTR(S) MAKE_CSTRING(CharT, S) + +using namespace std::__format_spec; + +// Note the tests were originally written for a parser, in this test the +// formatter is the parser. +template +using parser = __formatter_string; + +#include "test_exception.h" // requires parser + +template +struct Expected { + CharT fill; + _Flags::_Alignment alignment; + uint32_t width; + bool width_as_arg; + uint32_t precision; + bool precision_as_arg; + _Flags::_Type type; +}; + +template +constexpr void test(Expected expected, size_t size, + std::basic_string_view fmt) { + // Initialize formatter with sufficient arguments to avoid the parsing to fail + // due to insufficient arguments. + std::basic_format_parse_context parse_ctx(fmt, + std::__format::__number_max); + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + parser formatter; + auto it = formatter.parse(parse_ctx); + + assert(begin == parse_ctx.begin()); + assert(end == parse_ctx.end()); + + assert(begin + size == it); + assert(formatter.__fill == expected.fill); + assert(formatter.__alignment == expected.alignment); + assert(formatter.__sign == _Flags::_Sign::__default); + assert(formatter.__alternate_form == false); + assert(formatter.__zero_padding == false); + assert(formatter.__width == expected.width); + assert(formatter.__width_as_arg == expected.width_as_arg); + assert(formatter.__precision == expected.precision); + assert(formatter.__precision_as_arg == expected.precision_as_arg); + assert(formatter.__locale_specific_form == false); + assert(formatter.__type == expected.type); +} + +template +constexpr void test(Expected expected, size_t size, const CharT* f) { + // The format-spec is valid if completely consumed or terminates at a '}'. + // The valid inputs all end with a '}'. The test is executed twice: + // - first with the terminating '}', + // - second consuming the entire input. + std::basic_string_view fmt{f}; + assert(fmt.back() == CharT('}') && "Pre-condition failure"); + + test(expected, size, fmt); + fmt.remove_suffix(1); + test(expected, size, fmt); +} + +template +constexpr void test() { + parser formatter; + + assert(formatter.__fill == CharT(' ')); + assert(formatter.__alignment == _Flags::_Alignment::__left); + assert(formatter.__sign == _Flags::_Sign::__default); + assert(formatter.__alternate_form == false); + assert(formatter.__zero_padding == false); + assert(formatter.__width == 0); + assert(formatter.__width_as_arg == false); + assert(formatter.__precision == std::__format::__number_max); + assert(formatter.__precision_as_arg == true); + assert(formatter.__locale_specific_form == false); + assert(formatter.__type == _Flags::_Type::__default); + + test({CharT(' '), _Flags::_Alignment::__left, 0, false, + std::__format::__number_max, true, _Flags::_Type::__default}, + 0, CSTR("}")); + test({CharT(' '), _Flags::_Alignment::__center, 0, false, + std::__format::__number_max, true, _Flags::_Type::__default}, + 1, "^}"); + test({CharT('-'), _Flags::_Alignment::__right, 0, false, + std::__format::__number_max, true, _Flags::_Type::__default}, + 2, CSTR("->}")); + test({CharT('-'), _Flags::_Alignment::__right, 1, false, + std::__format::__number_max, true, _Flags::_Type::__default}, + 3, CSTR("->1}")); + test({CharT('-'), _Flags::_Alignment::__right, 0, true, + std::__format::__number_max, true, _Flags::_Type::__default}, + 4, CSTR("->{}}")); + test({CharT('-'), _Flags::_Alignment::__right, 1, false, 0, false, + _Flags::_Type::__default}, + 5, CSTR("->1.0}")); + test({CharT(' '), _Flags::_Alignment::__left, 0, true, 1, true, + _Flags::_Type::__default}, + 5, CSTR("{}.{}}")); + test({CharT(' '), _Flags::_Alignment::__left, 10, true, 9, true, + _Flags::_Type::__default}, + 8, CSTR("{10}.{9}}")); + test({CharT(' '), _Flags::_Alignment::__left, 10, true, 9, true, + _Flags::_Type::__string}, + 9, CSTR("{10}.{9}s}")); + + test_exception("A sign field isn't allowed in this format-spec", CSTR("+")); + test_exception("A sign field isn't allowed in this format-spec", CSTR("-")); + test_exception("A sign field isn't allowed in this format-spec", CSTR(" ")); + test_exception("An alternate form field isn't allowed in this format-spec", + CSTR("#")); + test_exception("A zero-padding field isn't allowed in this format-spec", + CSTR("0")); + test_exception( + "A locale-specific form field isn't allowed in this format-spec", + CSTR("L")); + test_exception("The format-spec should consume the input or end with a '}'", + CSTR("ss")); + + { + const char* expected = + "The format-spec type has a type not supported for a string argument"; + test_exception(expected, CSTR("A}")); + test_exception(expected, CSTR("B}")); + test_exception(expected, CSTR("E}")); + test_exception(expected, CSTR("F}")); + test_exception(expected, CSTR("G}")); + test_exception(expected, CSTR("X}")); + test_exception(expected, CSTR("a}")); + test_exception(expected, CSTR("b}")); + test_exception(expected, CSTR("c}")); + test_exception(expected, CSTR("d}")); + test_exception(expected, CSTR("e}")); + test_exception(expected, CSTR("f}")); + test_exception(expected, CSTR("g}")); + test_exception(expected, CSTR("o}")); + test_exception(expected, CSTR("p}")); + test_exception(expected, CSTR("x}")); + } +} + +constexpr bool test() { + test(); + test(); +#ifndef _LIBCPP_HAS_NO_CHAR8_T + test(); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + test(); + test(); +#endif + return true; +} + +int main(int, char**) { +#ifndef _WIN32 + // Make sure the parsers match the expectations. The layout of the + // subobjects is chosen to minimize the size required. + static_assert(sizeof(parser) == 3 * sizeof(uint32_t)); + static_assert( + sizeof(parser) == + (sizeof(wchar_t) <= 2 ? 3 * sizeof(uint32_t) : 4 * sizeof(uint32_t))); +#ifndef _LIBCPP_HAS_NO_CHAR8_T + static_assert(sizeof(parser) == 3 * sizeof(uint32_t)); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + static_assert(sizeof(parser) == 3 * sizeof(uint32_t)); + static_assert(sizeof(parser) == 4 * sizeof(uint32_t)); +#endif +#endif + + test(); + static_assert(test()); + + return 0; +}