diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -136,6 +136,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_base diff --git a/libcxx/include/__format/format_string.h b/libcxx/include/__format/format_string.h --- a/libcxx/include/__format/format_string.h +++ b/libcxx/include/__format/format_string.h @@ -20,9 +20,6 @@ #pragma GCC system_header #endif -_LIBCPP_PUSH_MACROS -#include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD #if _LIBCPP_STD_VER > 17 @@ -169,6 +166,4 @@ _LIBCPP_END_NAMESPACE_STD -_LIBCPP_POP_MACROS - #endif // _LIBCPP___FORMAT_FORMAT_STRING_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 @@ -10,10 +10,16 @@ #ifndef _LIBCPP___FORMAT_FORMATTER_H #define _LIBCPP___FORMAT_FORMATTER_H +#include <__algorithm/copy.h> +#include <__algorithm/fill_n.h> #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 @@ -54,6 +60,136 @@ } }; +namespace __formatter { + +/** The character types that formatters are specialized for. */ +template +concept __char_type = same_as<_CharT, char> || same_as<_CharT, wchar_t>; + +struct _LIBCPP_TEMPLATE_VIS __padding_size_result { + size_t __before; + size_t __after; +}; + +_LIBCPP_HIDE_FROM_ABI 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 elements in range [@a __first, @a __last) can differ + * from the type of @a __fill. Integer output uses @c std::to_chars for its + * conversion, which means the [@a __first, @a __last) always contains elements + * of the type @c char. + */ +template +_LIBCPP_HIDE_FROM_ABI 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 +_LIBCPP_HIDE_FROM_ABI 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::__string_alignment<_CharT> __format_traits = + __format_spec::__get_string_alignment(__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) #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,154 @@ +// -*- 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) + +namespace __format_spec { + +template <__formatter::__char_type _CharT> +class _LIBCPP_TEMPLATE_VIS __formatter_string : public __parser_string<_CharT> { +public: + _LIBCPP_HIDE_FROM_ABI 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_HIDE_FROM_ABI 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_HIDE_FROM_ABI 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_HIDE_FROM_ABI 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_HIDE_FROM_ABI 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) + +#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 @@ -235,6 +235,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 @@ -277,8 +277,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 <__variant/monostate.h> #include #include @@ -354,43 +355,6 @@ } }; -template -struct _LIBCPP_TEMPLATE_VIS __formatter_c_string { - _LIBCPP_HIDE_FROM_ABI - auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { - // TODO FMT Implement this function. - return __parse_ctx.begin(); - } - - _LIBCPP_HIDE_FROM_ABI - 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_HIDE_FROM_ABI - auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { - // TODO FMT Implement this function. - return __parse_ctx.begin(); - } - - _LIBCPP_HIDE_FROM_ABI - 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_HIDE_FROM_ABI @@ -470,54 +434,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_HIDE_FROM_ABI 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_HIDE_FROM_ABI 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_HIDE_FROM_ABI 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 @@ -443,6 +443,7 @@ module format_parse_context { private header "__format/format_parse_context.h" } module format_string { private header "__format/format_string.h" } module formatter { private header "__format/formatter.h" } + module formatter_string { private header "__format/formatter_string.h" } module parser_std_format_spec { private header "__format/parser_std_format_spec.h" } } } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_string.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_string.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_string.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__format/formatter_string.h'}} +#include <__format/formatter_string.h> diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp @@ -72,6 +72,29 @@ std::basic_string s(CSTR("abc\0abc"), 7); test_termination_condition(STR("abc"), STR("}"), s.c_str()); + + test_termination_condition(STR("world"), STR("}"), CSTR("world")); + test_termination_condition(STR("world"), STR("_>}"), + CSTR("world")); + + test_termination_condition(STR(" world"), STR(">8}"), + CSTR("world")); + test_termination_condition(STR("___world"), STR("_>8}"), + CSTR("world")); + test_termination_condition(STR("_world__"), STR("_^8}"), + CSTR("world")); + test_termination_condition(STR("world___"), STR("_<8}"), + CSTR("world")); + + test_termination_condition(STR("world"), STR(".5}"), + CSTR("world")); + test_termination_condition(STR("unive"), STR(".5}"), + CSTR("universe")); + + test_termination_condition(STR("%world%"), STR("%^7.7}"), + CSTR("world")); + test_termination_condition(STR("univers"), STR("%^7.7}"), + CSTR("universe")); } int main(int, char**) { diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp @@ -96,6 +96,20 @@ std::basic_string s(CSTR("abc\0abc"), 7); test_helper_wrapper<"abc\0abc">(s, STR("}")); + + test_helper_wrapper<"world">(STR("world"), STR("}")); + test_helper_wrapper<"world">(STR("world"), STR("_>}")); + + test_helper_wrapper<"world">(STR(" world"), STR(">8}")); + test_helper_wrapper<"world">(STR("___world"), STR("_>8}")); + test_helper_wrapper<"world">(STR("_world__"), STR("_^8}")); + test_helper_wrapper<"world">(STR("world___"), STR("_<8}")); + + test_helper_wrapper<"world">(STR("world"), STR(".5}")); + test_helper_wrapper<"universe">(STR("unive"), STR(".5}")); + + test_helper_wrapper<"world">(STR("%world%"), STR("%^7.7}")); + test_helper_wrapper<"universe">(STR("univers"), STR("%^7.7}")); } int main(int, char**) { diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp @@ -83,6 +83,30 @@ std::basic_string s(CSTR("abc\0abc"), 7); test_termination_condition(s, STR("}"), s); + + test_termination_condition(STR("world"), STR("}"), + STR("world")); + test_termination_condition(STR("world"), STR("_>}"), + STR("world")); + + test_termination_condition(STR(" world"), STR(">8}"), + STR("world")); + test_termination_condition(STR("___world"), STR("_>8}"), + STR("world")); + test_termination_condition(STR("_world__"), STR("_^8}"), + STR("world")); + test_termination_condition(STR("world___"), STR("_<8}"), + STR("world")); + + test_termination_condition(STR("world"), STR(".5}"), + STR("world")); + test_termination_condition(STR("unive"), STR(".5}"), + STR("universe")); + + test_termination_condition(STR("%world%"), STR("%^7.7}"), + STR("world")); + test_termination_condition(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 @@ -27,6 +27,7 @@ #ifndef _LIBCPP_HAS_NO_LOCALIZATION #include #endif +#include #include "test_macros.h" #include "format_tests.h" diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h --- a/libcxx/test/std/utilities/format/format.functions/format_tests.h +++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h @@ -10,10 +10,256 @@ #include "make_string.h" -#define STR(S) MAKE_STRING(CharT, S) - +// In this file the following template types are used: // TestFunction must be callable as check(expected-result, string-to-format, args-to-format...) // ExceptionTest must be callable as check_exception(expected-exception, string-to-format, args-to-format...) + +#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; +} + +template +void format_test_string(T world, T universe, TestFunction check, + ExceptionTest check_exception) { + + // *** Valid input tests *** + // Unsed argument is ignored. TODO FMT what does the Standard mandate? + check(STR("hello world"), STR("hello {}"), world, universe); + check(STR("hello world and universe"), STR("hello {} and {}"), world, + universe); + check(STR("hello world"), STR("hello {0}"), world, universe); + check(STR("hello universe"), STR("hello {1}"), world, universe); + check(STR("hello universe and world"), STR("hello {1} and {0}"), world, + universe); + + check(STR("hello world"), STR("hello {:_>}"), world); + check(STR("hello world"), STR("hello {:>8}"), world); + check(STR("hello ___world"), STR("hello {:_>8}"), world); + check(STR("hello _world__"), STR("hello {:_^8}"), world); + check(STR("hello world___"), STR("hello {:_<8}"), world); + + check(STR("hello >>>world"), STR("hello {:>>8}"), world); + check(STR("hello <<8}"), world); + check(STR("hello ^^^world"), STR("hello {:^>8}"), world); + + check(STR("hello $world"), STR("hello {:$>{}}"), world, 6); + check(STR("hello $world"), STR("hello {0:$>{1}}"), world, 6); + check(STR("hello $world"), STR("hello {1:$>{0}}"), 6, world); + + check(STR("hello world"), STR("hello {:.5}"), world); + check(STR("hello unive"), STR("hello {:.5}"), universe); + + check(STR("hello univer"), STR("hello {:.{}}"), universe, 6); + check(STR("hello univer"), STR("hello {0:.{1}}"), universe, 6); + check(STR("hello univer"), STR("hello {1:.{0}}"), 6, universe); + + check(STR("hello %world%"), STR("hello {:%^7.7}"), world); + check(STR("hello univers"), STR("hello {:%^7.7}"), universe); + check(STR("hello %world%"), STR("hello {:%^{}.{}}"), world, 7, 7); + check(STR("hello %world%"), STR("hello {0:%^{1}.{2}}"), world, 7, 7); + check(STR("hello %world%"), STR("hello {0:%^{2}.{1}}"), world, 7, 7); + check(STR("hello %world%"), STR("hello {1:%^{0}.{2}}"), 7, world, 7); + + check(STR("hello world"), STR("hello {:_>s}"), world); + check(STR("hello $world"), STR("hello {:$>{}s}"), world, 6); + check(STR("hello world"), STR("hello {:.5s}"), world); + check(STR("hello univer"), STR("hello {:.{}s}"), universe, 6); + check(STR("hello %world%"), STR("hello {:%^7.7s}"), world); + + check(STR("hello #####uni"), STR("hello {:#>8.3s}"), universe); + check(STR("hello ##uni###"), STR("hello {:#^8.3s}"), universe); + check(STR("hello uni#####"), STR("hello {:#<8.3s}"), universe); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("hello {:-}"), world); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("hello {:#}"), world); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", + STR("hello {:0}"), world); + + // *** width *** +#if _LIBCPP_VERSION + // This limit isn't specified in the Standard. + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + check_exception("The numeric value of the format-spec is too large", + STR("{:2147483648}"), world); + check_exception("The numeric value of the format-spec is too large", + STR("{:5000000000}"), world); + check_exception("The numeric value of the format-spec is too large", + STR("{:10000000000}"), world); +#endif + + check_exception( + "A format-spec width field replacement should have a positive value", + STR("hello {:{}}"), world, 0); + check_exception( + "A format-spec arg-id replacement shouldn't have a negative value", + STR("hello {:{}}"), world, -1); + check_exception( + "A format-spec arg-id replacement exceeds the maximum supported value", + STR("hello {:{}}"), world, -1u); + check_exception("Argument index out of bounds", STR("hello {:{}}"), world); + check_exception( + "A format-spec arg-id replacement argument isn't an integral type", + STR("hello {:{}}"), world, universe); + check_exception( + "Using manual argument numbering in automatic argument numbering mode", + STR("hello {:{0}}"), world, 1); + check_exception( + "Using automatic argument numbering in manual argument numbering mode", + STR("hello {0:{}}"), world, 1); + + // *** precision *** + check_exception("A format-spec precision field shouldn't have a leading zero", + STR("hello {:.01}"), world); + +#if _LIBCPP_VERSION + // This limit isn't specified in the Standard. + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + check_exception("The numeric value of the format-spec is too large", + STR("{:.2147483648}"), world); + check_exception("The numeric value of the format-spec is too large", + STR("{:.5000000000}"), world); + check_exception("The numeric value of the format-spec is too large", + STR("{:.10000000000}"), world); +#endif + + // Precision 0 allowed, but not useful for string arguments. + check(STR("hello "), STR("hello {:.{}}"), world, 0); + check_exception( + "A format-spec arg-id replacement shouldn't have a negative value", + STR("hello {:.{}}"), world, -1); + check_exception( + "A format-spec arg-id replacement exceeds the maximum supported value", + STR("hello {:.{}}"), world, -1u); + check_exception("Argument index out of bounds", STR("hello {:.{}}"), world); + check_exception( + "A format-spec arg-id replacement argument isn't an integral type", + STR("hello {:.{}}"), world, universe); + check_exception( + "Using manual argument numbering in automatic argument numbering mode", + STR("hello {:.{0}}"), world, 1); + check_exception( + "Using automatic argument numbering in manual argument numbering mode", + STR("hello {0:.{}}"), world, 1); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("hello {:L}"), world); + + // *** type *** + for (const auto& fmt : invalid_types("s")) + check_exception( + "The format-spec type has a type not supported for a string argument", + fmt, world); +} + +template +void format_test_string_unicode(TestFunction check) { +#ifndef _LIBCPP_HAS_NO_UNICODE + // ß requires one column + check(STR("aßc"), STR("{}"), STR("aßc")); + + check(STR("aßc"), STR("{:.3}"), STR("aßc")); + check(STR("aß"), STR("{:.2}"), STR("aßc")); + check(STR("a"), STR("{:.1}"), STR("aßc")); + + check(STR("aßc"), STR("{:3.3}"), STR("aßc")); + check(STR("aß"), STR("{:2.2}"), STR("aßc")); + check(STR("a"), STR("{:1.1}"), STR("aßc")); + + check(STR("aßc---"), STR("{:-<6}"), STR("aßc")); + check(STR("-aßc--"), STR("{:-^6}"), STR("aßc")); + check(STR("---aßc"), STR("{:->6}"), STR("aßc")); + + // \u1000 requires two columns + check(STR("a\u1110c"), STR("{}"), STR("a\u1110c")); + + check(STR("a\u1100c"), STR("{:.4}"), STR("a\u1100c")); + check(STR("a\u1100"), STR("{:.3}"), STR("a\u1100c")); + check(STR("a"), STR("{:.2}"), STR("a\u1100c")); + check(STR("a"), STR("{:.1}"), STR("a\u1100c")); + + check(STR("a\u1100c"), STR("{:-<4.4}"), STR("a\u1100c")); + check(STR("a\u1100"), STR("{:-<3.3}"), STR("a\u1100c")); + check(STR("a-"), STR("{:-<2.2}"), STR("a\u1100c")); + check(STR("a"), STR("{:-<1.1}"), STR("a\u1100c")); + + check(STR("a\u1110c---"), STR("{:-<7}"), STR("a\u1110c")); + check(STR("-a\u1110c--"), STR("{:-^7}"), STR("a\u1110c")); + check(STR("---a\u1110c"), STR("{:->7}"), STR("a\u1110c")); +#else + (void)check; +#endif +} + +template +void format_string_tests(TestFunction check, ExceptionTest check_exception) { + 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. + format_test_string(world.c_str(), universe.c_str(), check, + check_exception); + format_test_string(const_cast(world.c_str()), + const_cast(universe.c_str()), check, + check_exception); + format_test_string(std::basic_string_view(world), + std::basic_string_view(universe), check, + check_exception); + format_test_string(world, universe, check, check_exception); + format_test_string_unicode(check); +} + template void format_tests(TestFunction check, ExceptionTest check_exception) { // *** Test escaping *** @@ -71,6 +317,7 @@ std::basic_string_view data = buffer; check(STR("hello world"), STR("hello {}"), data); } + format_string_tests(check, check_exception); // *** Test Boolean format argument *** check(STR("hello 01"), STR("hello {}{}"), false, true); 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 @@ -20,6 +20,7 @@ #include #include +#include #include "test_macros.h" #include "format_tests.h" 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 @@ -18,6 +18,7 @@ #include #include +#include #include "test_macros.h" #include "format_tests.h"