diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -140,6 +140,7 @@ __format/format_parse_context.h __format/format_string.h __format/formatter.h + __format/formatter_char.h __format/formatter_integer.h __format/formatter_integral.h __format/formatter_string.h diff --git a/libcxx/include/__format/formatter_char.h b/libcxx/include/__format/formatter_char.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/formatter_char.h @@ -0,0 +1,103 @@ +// -*- 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_CHAR_H +#define _LIBCPP___FORMAT_FORMATTER_CHAR_H + +#include <__availability> +#include <__config> +#include <__format/format_error.h> +#include <__format/format_fwd.h> +#include <__format/formatter.h> +#include <__format/formatter_integral.h> +#include <__format/parser_std_format_spec.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_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 +class _LIBCPP_TEMPLATE_VIS __parser_char : public __parser_integral<_CharT> { +public: + _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + auto __it = __parser_integral<_CharT>::__parse(__parse_ctx); + + switch (this->__type) { + case _Flags::_Type::__default: + this->__type = _Flags::_Type::__char; + [[fallthrough]]; + case _Flags::_Type::__char: + this->__handle_char(); + break; + + case _Flags::_Type::__binary_lower_case: + case _Flags::_Type::__binary_upper_case: + case _Flags::_Type::__octal: + case _Flags::_Type::__decimal: + case _Flags::_Type::__hexadecimal_lower_case: + case _Flags::_Type::__hexadecimal_upper_case: + this->__handle_integer(); + break; + + default: + __throw_format_error( + "The format-spec type has a type not supported for a char argument"); + } + + return __it; + } +}; + +template +using __formatter_char = __formatter_integral<__parser_char<_CharT>>; + +} // namespace __format_spec + +// [format.formatter.spec]/2.1 The specializations + +template <> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_char {}; + +template <> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_char { + using _Base = __format_spec::__formatter_char; + + _LIBCPP_HIDE_FROM_ABI auto format(char __value, auto& __ctx) + -> decltype(__ctx.out()) { + return _Base::format(static_cast(__value), __ctx); + } +}; + +template <> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_char {}; + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_FORMATTER_CHAR_H diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -279,6 +279,7 @@ #include <__format/format_parse_context.h> #include <__format/format_string.h> #include <__format/formatter.h> +#include <__format/formatter_char.h> #include <__format/formatter_integer.h> #include <__format/formatter_string.h> #include <__format/parser_std_format_spec.h> @@ -338,24 +339,7 @@ make_wformat_args(const _Args&... __args) { return _VSTD::make_format_args(__args...); } - namespace __format { -template -struct _LIBCPP_TEMPLATE_VIS __formatter_char { - _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(_Tp __c, auto& __ctx) -> decltype(__ctx.out()) { - // TODO FMT Implement the parsed formatting arguments. - auto __out_it = __ctx.out(); - *__out_it++ = _CharT(__c); - return __out_it; - } -}; template requires(is_arithmetic_v<_Tp> && @@ -405,20 +389,6 @@ // These specializations are helper stubs and not proper formatters. // TODO FMT Implement the proper formatter specializations. -// [format.formatter.spec]/2.1 The specializations - -template <> -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_char {}; - -template <> -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_char {}; - -template <> -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_char {}; - // [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 @@ -450,6 +450,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_char { private header "__format/formatter_char.h" } module formatter_integer { private header "__format/formatter_integer.h" } module formatter_integral { private header "__format/formatter_integral.h" } module formatter_string { private header "__format/formatter_string.h" } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_char.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_char.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_char.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_char.h'}} +#include <__format/formatter_char.h> diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp @@ -0,0 +1,452 @@ +//===----------------------------------------------------------------------===// +// 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 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// + +// Tests the parsing of the format string as specified in [format.string.std]. +// It validates whether the std-format-spec is valid for a char type. + +#include +#include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +# include +#endif + +#include "concepts_precision.h" +#include "test_macros.h" +#include "make_string.h" +#include "test_exception.h" + +#define CSTR(S) MAKE_CSTRING(CharT, S) + +using namespace std::__format_spec; + +template +using Parser = __parser_char; + +template +struct Expected { + CharT fill = CharT(' '); + _Flags::_Alignment alignment = _Flags::_Alignment::__left; + _Flags::_Sign sign = _Flags::_Sign::__default; + bool alternate_form = false; + bool zero_padding = false; + uint32_t width = 0; + bool width_as_arg = false; + bool locale_specific_form = false; + _Flags::_Type type = _Flags::_Type::__char; +}; + +template +constexpr void test(Expected expected, size_t size, + std::basic_string_view fmt) { + // Initialize parser 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 parser; + auto it = parser.parse(parse_ctx); + + assert(begin == parse_ctx.begin()); + assert(end == parse_ctx.end()); + + assert(begin + size == it); + assert(parser.__fill == expected.fill); + assert(parser.__alignment == expected.alignment); + assert(parser.__sign == expected.sign); + assert(parser.__alternate_form == expected.alternate_form); + assert(parser.__zero_padding == expected.zero_padding); + assert(parser.__width == expected.width); + assert(parser.__width_as_arg == expected.width_as_arg); + assert(parser.__locale_specific_form == expected.locale_specific_form); + assert(parser.__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_as_char() { + + test({}, 1, CSTR("c}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}")); + test({.alignment = _Flags::_Alignment::__center}, 1, "^}"); + test({.alignment = _Flags::_Alignment::__right}, 1, ">}"); + + test({.alignment = _Flags::_Alignment::__left}, 2, CSTR("c}"); + + test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 2, + CSTR("L<}")); + test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 2, + CSTR("#^}")); + test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 2, + CSTR("0>}")); + + test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 3, + CSTR("Lc}")); + + // *** Sign *** + 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>( + "A sign field isn't allowed in this format-spec", CSTR("-c")); + test_exception>( + "A sign field isn't allowed in this format-spec", CSTR("+c")); + test_exception>( + "A sign field isn't allowed in this format-spec", CSTR(" c")); + + // *** Alternate form *** + test_exception>( + "An alternate form field isn't allowed in this format-spec", CSTR("#}")); + test_exception>( + "An alternate form field isn't allowed in this format-spec", CSTR("#c}")); + + // *** Zero padding *** + test_exception>( + "A zero-padding field isn't allowed in this format-spec", CSTR("0}")); + test_exception>( + "A zero-padding field isn't allowed in this format-spec", CSTR("0c}")); + + // *** Width *** + test({.width = 0, .width_as_arg = false}, 0, CSTR("}")); + test({.width = 1, .width_as_arg = false}, 1, CSTR("1}")); + test({.width = 10, .width_as_arg = false}, 2, CSTR("10}")); + test({.width = 1000, .width_as_arg = false}, 4, CSTR("1000}")); + test({.width = 1000000, .width_as_arg = false}, 7, CSTR("1000000}")); + + test({.width = 0, .width_as_arg = true}, 2, CSTR("{}}")); + test({.width = 0, .width_as_arg = true}, 3, CSTR("{0}}")); + test({.width = 1, .width_as_arg = true}, 3, CSTR("{1}}")); + + test_exception>( + "A format-spec width field shouldn't have a leading zero", CSTR("00")); + + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({.width = 2'147'483'647, .width_as_arg = false}, 10, + CSTR("2147483647}")); + test_exception>( + "The numeric value of the format-spec is too large", CSTR("2147483648")); + test_exception>( + "The numeric value of the format-spec is too large", CSTR("5000000000")); + test_exception>( + "The numeric value of the format-spec is too large", CSTR("10000000000")); + + test_exception>("End of input while parsing format-spec arg-id", + CSTR("{")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{0")); + test_exception>( + "The arg-id of the format-spec starts with an invalid character", + CSTR("{a")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{1")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{9")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{9:")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{9a")); + + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + // Note the static_assert tests whether the arg-id is valid. + // Therefore the following should be true arg-id < __format::__number_max. + test({.width = 2'147'483'646, .width_as_arg = true}, 12, + CSTR("{2147483646}}")); + test_exception>( + "The numeric value of the format-spec is too large", + CSTR("{2147483648}")); + test_exception>( + "The numeric value of the format-spec is too large", + CSTR("{5000000000}")); + test_exception>( + "The numeric value of the format-spec is too large", + CSTR("{10000000000}")); + + // *** Precision *** + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".")); + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".1")); + + // *** Locale-specific form *** + // Note the flag is allowed, but has no effect. + test({.locale_specific_form = true}, 1, CSTR("L}")); + test({.locale_specific_form = true}, 2, CSTR("Lc}")); +} + +template +constexpr void test_as_integer() { + + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__decimal}, + 1, CSTR("d}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left, + .type = _Flags::_Type::__decimal}, + 2, CSTR("d}"); + + test({.fill = CharT('L'), + .alignment = _Flags::_Alignment::__left, + .type = _Flags::_Type::__decimal}, + 3, CSTR("Ld}")); + + // *** Sign *** + test({.alignment = _Flags::_Alignment::__right, + .sign = _Flags::_Sign::__minus, + .type = _Flags::_Type::__decimal}, + 2, CSTR("-d}")); + test({.alignment = _Flags::_Alignment::__right, + .sign = _Flags::_Sign::__plus, + .type = _Flags::_Type::__decimal}, + 2, CSTR("+d}")); + test({.alignment = _Flags::_Alignment::__right, + .sign = _Flags::_Sign::__space, + .type = _Flags::_Type::__decimal}, + 2, CSTR(" d}")); + + // *** Alternate form *** + test({.alignment = _Flags::_Alignment::__right, + .alternate_form = true, + .type = _Flags::_Type::__decimal}, + 2, CSTR("#d}")); + + // *** Zero padding *** + test({.alignment = _Flags::_Alignment::__default, + .zero_padding = true, + .type = _Flags::_Type::__decimal}, + 2, CSTR("0d}")); + test({.alignment = _Flags::_Alignment::__center, + .type = _Flags::_Type::__decimal}, + 3, CSTR("^0d}")); + + // *** Width *** + test({.alignment = _Flags::_Alignment::__right, + .width = 0, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 1, CSTR("d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 2, CSTR("1d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 10, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 3, CSTR("10d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1000, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 5, CSTR("1000d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1000000, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 8, CSTR("1000000d}")); + + test({.alignment = _Flags::_Alignment::__right, + .width = 0, + .width_as_arg = true, + .type = _Flags::_Type::__decimal}, + 3, CSTR("{}d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 0, + .width_as_arg = true, + .type = _Flags::_Type::__decimal}, + 4, CSTR("{0}d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1, + .width_as_arg = true, + .type = _Flags::_Type::__decimal}, + 4, CSTR("{1}d}")); + + // *** Precision *** + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".")); + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".1")); + + // *** Locale-specific form *** + test({.alignment = _Flags::_Alignment::__right, + .locale_specific_form = true, + .type = _Flags::_Type::__decimal}, + 2, CSTR("Ld}")); +} + +template +constexpr void test() { + Parser parser; + + assert(parser.__fill == CharT(' ')); + assert(parser.__alignment == _Flags::_Alignment::__default); + assert(parser.__sign == _Flags::_Sign::__default); + assert(parser.__alternate_form == false); + assert(parser.__zero_padding == false); + assert(parser.__width == 0); + assert(parser.__width_as_arg == false); + static_assert(!has_precision); + static_assert(!has_precision_as_arg); + assert(parser.__locale_specific_form == false); + assert(parser.__type == _Flags::_Type::__default); + + test({}, 0, CSTR("}")); + + test_as_char(); + test_as_integer(); + + // *** Type *** + { + const char* unsuported_type = + "The format-spec type has a type not supported for a char argument"; + const char* not_a_type = + "The format-spec should consume the input or end with a '}'"; + + test_exception>(unsuported_type, CSTR("A}")); + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__binary_upper_case}, + 1, CSTR("B}")); + test_exception>(not_a_type, CSTR("C}")); + test_exception>(not_a_type, CSTR("D}")); + test_exception>(unsuported_type, CSTR("E}")); + test_exception>(unsuported_type, CSTR("F}")); + test_exception>(unsuported_type, CSTR("G}")); + test_exception>(not_a_type, CSTR("H}")); + test_exception>(not_a_type, CSTR("I}")); + test_exception>(not_a_type, CSTR("J}")); + test_exception>(not_a_type, CSTR("K}")); + test({.locale_specific_form = true}, 1, CSTR("L}")); + test_exception>(not_a_type, CSTR("M}")); + test_exception>(not_a_type, CSTR("N}")); + test_exception>(not_a_type, CSTR("O}")); + test_exception>(not_a_type, CSTR("P}")); + test_exception>(not_a_type, CSTR("Q}")); + test_exception>(not_a_type, CSTR("R}")); + test_exception>(not_a_type, CSTR("S}")); + test_exception>(not_a_type, CSTR("T}")); + test_exception>(not_a_type, CSTR("U}")); + test_exception>(not_a_type, CSTR("V}")); + test_exception>(not_a_type, CSTR("W}")); + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__hexadecimal_upper_case}, + 1, CSTR("X}")); + test_exception>(not_a_type, CSTR("Y}")); + test_exception>(not_a_type, CSTR("Z}")); + + test_exception>(unsuported_type, CSTR("a}")); + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__binary_lower_case}, + 1, CSTR("b}")); + test({.type = _Flags::_Type::__char}, 1, CSTR("c}")); + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__decimal}, + 1, CSTR("d}")); + test_exception>(unsuported_type, CSTR("e}")); + test_exception>(unsuported_type, CSTR("f}")); + test_exception>(unsuported_type, CSTR("g}")); + test_exception>(not_a_type, CSTR("h}")); + test_exception>(not_a_type, CSTR("i}")); + test_exception>(not_a_type, CSTR("j}")); + test_exception>(not_a_type, CSTR("k}")); + test_exception>(not_a_type, CSTR("l}")); + test_exception>(not_a_type, CSTR("m}")); + test_exception>(not_a_type, CSTR("n}")); + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__octal}, + 1, CSTR("o}")); + test_exception>(unsuported_type, CSTR("p}")); + test_exception>(not_a_type, CSTR("q}")); + test_exception>(not_a_type, CSTR("r}")); + test_exception>(unsuported_type, CSTR("s}")); + test_exception>(not_a_type, CSTR("t}")); + test_exception>(not_a_type, CSTR("u}")); + test_exception>(not_a_type, CSTR("v}")); + test_exception>(not_a_type, CSTR("w}")); + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__hexadecimal_lower_case}, + 1, CSTR("x}")); + test_exception>(not_a_type, CSTR("y}")); + test_exception>(not_a_type, CSTR("z}")); + } + + // **** General *** + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR("ss")); +} + +constexpr bool test() { + test(); + test(); + + return true; +} + +int main(int, char**) { +#ifndef _WIN32 + // TODO FMT Investigate why this doesn't work. + // (Wait until LWG-3576 has been resolved.) + // Make sure the parsers match the expectations. The layout of the + // subobjects is chosen to minimize the size required. + static_assert(sizeof(Parser) == 2 * sizeof(uint32_t)); + static_assert( + sizeof(Parser) == + (sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t))); +#endif + + test(); + static_assert(test()); + + return 0; +} 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 @@ -587,6 +587,155 @@ // TODO FMT Add __uint128_t test after implementing full range. } +template +void format_test_char(TestFunction check, ExceptionTest check_exception) { + + // ***** Char type ***** + // *** align-fill & width *** + check(STR("answer is '* '"), STR("answer is '{:6}'"), CharT('*')); + check(STR("answer is ' *'"), STR("answer is '{:>6}'"), CharT('*')); + check(STR("answer is '* '"), STR("answer is '{:<6}'"), CharT('*')); + check(STR("answer is ' * '"), STR("answer is '{:^6}'"), CharT('*')); + + check(STR("answer is '* '"), STR("answer is '{:6c}'"), CharT('*')); + check(STR("answer is ' *'"), STR("answer is '{:>6c}'"), CharT('*')); + check(STR("answer is '* '"), STR("answer is '{:<6c}'"), CharT('*')); + check(STR("answer is ' * '"), STR("answer is '{:^6c}'"), CharT('*')); + + check(STR("answer is '-----*'"), STR("answer is '{:->6}'"), CharT('*')); + check(STR("answer is '*-----'"), STR("answer is '{:-<6}'"), CharT('*')); + check(STR("answer is '--*---'"), STR("answer is '{:-^6}'"), CharT('*')); + + check(STR("answer is '-----*'"), STR("answer is '{:->6c}'"), CharT('*')); + check(STR("answer is '*-----'"), STR("answer is '{:-<6c}'"), CharT('*')); + check(STR("answer is '--*---'"), STR("answer is '{:-^6c}'"), CharT('*')); + + // *** Sign *** + check_exception("A sign field isn't allowed in this format-spec", STR("{:-}"), + CharT('*')); + check_exception("A sign field isn't allowed in this format-spec", STR("{:+}"), + CharT('*')); + check_exception("A sign field isn't allowed in this format-spec", STR("{: }"), + CharT('*')); + + check_exception("A sign field isn't allowed in this format-spec", + STR("{:-c}"), CharT('*')); + check_exception("A sign field isn't allowed in this format-spec", + STR("{:+c}"), CharT('*')); + check_exception("A sign field isn't allowed in this format-spec", + STR("{: c}"), CharT('*')); + + // *** alternate form *** + check_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#}"), CharT('*')); + check_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#c}"), CharT('*')); + + // *** zero-padding *** + check_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0}"), CharT('*')); + check_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0c}"), CharT('*')); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.}"), CharT('*')); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0}"), CharT('*')); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.42}"), CharT('*')); + + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.c}"), CharT('*')); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0c}"), CharT('*')); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.42c}"), CharT('*')); + + // *** locale-specific form *** + // Note it has no effect but it's allowed. + check(STR("answer is '*'"), STR("answer is '{:L}'"), '*'); + check(STR("answer is '*'"), STR("answer is '{:Lc}'"), '*'); + + // *** type *** + for (const auto& fmt : invalid_types("bBcdoxX")) + check_exception( + "The format-spec type has a type not supported for a char argument", + fmt, CharT('*')); +} + +template +void format_test_char_as_integer(TestFunction check, + ExceptionTest check_exception) { + // *** align-fill & width *** + check(STR("answer is '42'"), STR("answer is '{:<1d}'"), CharT('*')); + + check(STR("answer is '42'"), STR("answer is '{:<2d}'"), CharT('*')); + check(STR("answer is '42 '"), STR("answer is '{:<3d}'"), CharT('*')); + + check(STR("answer is ' 42'"), STR("answer is '{:7d}'"), CharT('*')); + check(STR("answer is ' 42'"), STR("answer is '{:>7d}'"), CharT('*')); + check(STR("answer is '42 '"), STR("answer is '{:<7d}'"), CharT('*')); + check(STR("answer is ' 42 '"), STR("answer is '{:^7d}'"), CharT('*')); + + check(STR("answer is '*****42'"), STR("answer is '{:*>7d}'"), CharT('*')); + check(STR("answer is '42*****'"), STR("answer is '{:*<7d}'"), CharT('*')); + check(STR("answer is '**42***'"), STR("answer is '{:*^7d}'"), CharT('*')); + + // Test whether zero padding is ignored + check(STR("answer is ' 42'"), STR("answer is '{:>07d}'"), CharT('*')); + check(STR("answer is '42 '"), STR("answer is '{:<07d}'"), CharT('*')); + check(STR("answer is ' 42 '"), STR("answer is '{:^07d}'"), CharT('*')); + + // *** Sign *** + check(STR("answer is 42"), STR("answer is {:d}"), CharT('*')); + check(STR("answer is 42"), STR("answer is {:-d}"), CharT('*')); + check(STR("answer is +42"), STR("answer is {:+d}"), CharT('*')); + check(STR("answer is 42"), STR("answer is {: d}"), CharT('*')); + + // *** alternate form *** + check(STR("answer is +42"), STR("answer is {:+#d}"), CharT('*')); + check(STR("answer is +101010"), STR("answer is {:+b}"), CharT('*')); + check(STR("answer is +0b101010"), STR("answer is {:+#b}"), CharT('*')); + check(STR("answer is +0B101010"), STR("answer is {:+#B}"), CharT('*')); + check(STR("answer is +52"), STR("answer is {:+o}"), CharT('*')); + check(STR("answer is +052"), STR("answer is {:+#o}"), CharT('*')); + check(STR("answer is +2a"), STR("answer is {:+x}"), CharT('*')); + check(STR("answer is +0x2a"), STR("answer is {:+#x}"), CharT('*')); + check(STR("answer is +2A"), STR("answer is {:+X}"), CharT('*')); + check(STR("answer is +0X2A"), STR("answer is {:+#X}"), CharT('*')); + + // *** zero-padding & width *** + check(STR("answer is +00000000042"), STR("answer is {:+#012d}"), CharT('*')); + check(STR("answer is +00000101010"), STR("answer is {:+012b}"), CharT('*')); + check(STR("answer is +0b000101010"), STR("answer is {:+#012b}"), CharT('*')); + check(STR("answer is +0B000101010"), STR("answer is {:+#012B}"), CharT('*')); + check(STR("answer is +00000000052"), STR("answer is {:+012o}"), CharT('*')); + check(STR("answer is +00000000052"), STR("answer is {:+#012o}"), CharT('*')); + check(STR("answer is +0000000002a"), STR("answer is {:+012x}"), CharT('*')); + check(STR("answer is +0x00000002a"), STR("answer is {:+#012x}"), CharT('*')); + check(STR("answer is +0000000002A"), STR("answer is {:+012X}"), CharT('*')); + + check(STR("answer is +0X00000002A"), STR("answer is {:+#012X}"), CharT('*')); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.d}"), CharT('*')); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0d}"), CharT('*')); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.42d}"), CharT('*')); + + // *** locale-specific form *** + // See locale-specific_form.pass.cpp + + // *** type *** + for (const auto& fmt : invalid_types("bBcdoxX")) + check_exception( + "The format-spec type has a type not supported for a char argument", + fmt, '*'); +} + template void format_tests(TestFunction check, ExceptionTest check_exception) { // *** Test escaping *** @@ -622,6 +771,9 @@ check(STR("hello 09azAZ!"), STR("hello {}{}{}{}{}{}{}"), CharT('0'), CharT('9'), CharT('a'), CharT('z'), CharT('A'), CharT('Z'), CharT('!')); + format_test_char(check, check_exception); + format_test_char_as_integer(check, check_exception); + // *** Test string format argument *** { CharT buffer[] = {CharT('0'), CharT('9'), CharT('a'), CharT('z'),