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_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,111 @@ +// -*- 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_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 __parser_char : public __parser_integral<_CharT> { +public: + using _Base = __parser_integral<_CharT>; + + _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: + 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_INLINE_VISIBILITY 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) && !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_FORMATTER_CHAR_H diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -283,6 +283,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> @@ -343,24 +344,7 @@ make_wformat_args(const _Args&... __args) { return make_format_args(__args...); } - namespace __format { -template -struct _LIBCPP_TEMPLATE_VIS __formatter_char { - _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(_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> && @@ -410,20 +394,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 @@ -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_char { header "__format/formatter_char.h" } module formatter_integer { header "__format/formatter_integer.h" } module formatter_integral { header "__format/formatter_integral.h" } module formatter_string { header "__format/formatter_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 @@ -58,6 +58,154 @@ test(STR("hello 09azA"), STR("hello {}{}{}{}{}"), '0', '9', 'a', 'z', 'A'); } +template +void test_char() { + + // ***** Char type ***** + // *** align-fill & width *** + test(STR("answer is '* '"), STR("answer is '{:6}'"), CharT('*')); + test(STR("answer is ' *'"), STR("answer is '{:>6}'"), CharT('*')); + test(STR("answer is '* '"), STR("answer is '{:<6}'"), CharT('*')); + test(STR("answer is ' * '"), STR("answer is '{:^6}'"), CharT('*')); + + test(STR("answer is '* '"), STR("answer is '{:6c}'"), CharT('*')); + test(STR("answer is ' *'"), STR("answer is '{:>6c}'"), CharT('*')); + test(STR("answer is '* '"), STR("answer is '{:<6c}'"), CharT('*')); + test(STR("answer is ' * '"), STR("answer is '{:^6c}'"), CharT('*')); + + test(STR("answer is '-----*'"), STR("answer is '{:->6}'"), CharT('*')); + test(STR("answer is '*-----'"), STR("answer is '{:-<6}'"), CharT('*')); + test(STR("answer is '--*---'"), STR("answer is '{:-^6}'"), CharT('*')); + + test(STR("answer is '-----*'"), STR("answer is '{:->6c}'"), CharT('*')); + test(STR("answer is '*-----'"), STR("answer is '{:-<6c}'"), CharT('*')); + test(STR("answer is '--*---'"), STR("answer is '{:-^6c}'"), CharT('*')); + + // *** Sign *** + test_exception("A sign field isn't allowed in this format-spec", STR("{:-}"), + CharT('*')); + test_exception("A sign field isn't allowed in this format-spec", STR("{:+}"), + CharT('*')); + test_exception("A sign field isn't allowed in this format-spec", STR("{: }"), + CharT('*')); + + test_exception("A sign field isn't allowed in this format-spec", STR("{:-c}"), + CharT('*')); + test_exception("A sign field isn't allowed in this format-spec", STR("{:+c}"), + CharT('*')); + test_exception("A sign field isn't allowed in this format-spec", STR("{: c}"), + CharT('*')); + + // *** alternate form *** + test_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#}"), CharT('*')); + test_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#c}"), CharT('*')); + + // *** zero-padding *** + test_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0}"), CharT('*')); + test_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0c}"), CharT('*')); + + // *** precision *** + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.}"), CharT('*')); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0}"), CharT('*')); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42}"), CharT('*')); + + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.c}"), CharT('*')); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0c}"), CharT('*')); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42c}"), CharT('*')); + + // *** locale-specific form *** + // Note it has no effect but it's allowed. + test(STR("answer is '*'"), STR("answer is '{:L}'"), '*'); + test(STR("answer is '*'"), STR("answer is '{:Lc}'"), '*'); + + // *** type *** + for (const auto& fmt : invalid_types("bBcdoxX")) + test_exception( + "The format-spec type has a type not supported for a char argument", + fmt, CharT('*')); +} + +template +void test_char_as_integer() { + // *** align-fill & width *** + test(STR("answer is '42'"), STR("answer is '{:<1d}'"), CharT('*')); + + test(STR("answer is '42'"), STR("answer is '{:<2d}'"), CharT('*')); + test(STR("answer is '42 '"), STR("answer is '{:<3d}'"), CharT('*')); + + test(STR("answer is ' 42'"), STR("answer is '{:7d}'"), CharT('*')); + test(STR("answer is ' 42'"), STR("answer is '{:>7d}'"), CharT('*')); + test(STR("answer is '42 '"), STR("answer is '{:<7d}'"), CharT('*')); + test(STR("answer is ' 42 '"), STR("answer is '{:^7d}'"), CharT('*')); + + test(STR("answer is '*****42'"), STR("answer is '{:*>7d}'"), CharT('*')); + test(STR("answer is '42*****'"), STR("answer is '{:*<7d}'"), CharT('*')); + test(STR("answer is '**42***'"), STR("answer is '{:*^7d}'"), CharT('*')); + + // Test whether zero padding is ignored + test(STR("answer is ' 42'"), STR("answer is '{:>07d}'"), CharT('*')); + test(STR("answer is '42 '"), STR("answer is '{:<07d}'"), CharT('*')); + test(STR("answer is ' 42 '"), STR("answer is '{:^07d}'"), CharT('*')); + + // *** Sign *** + test(STR("answer is 42"), STR("answer is {:d}"), CharT('*')); + test(STR("answer is 42"), STR("answer is {:-d}"), CharT('*')); + test(STR("answer is +42"), STR("answer is {:+d}"), CharT('*')); + test(STR("answer is 42"), STR("answer is {: d}"), CharT('*')); + + // *** alternate form *** + test(STR("answer is +42"), STR("answer is {:+#d}"), CharT('*')); + test(STR("answer is +101010"), STR("answer is {:+b}"), CharT('*')); + test(STR("answer is +0b101010"), STR("answer is {:+#b}"), CharT('*')); + test(STR("answer is +0B101010"), STR("answer is {:+#B}"), CharT('*')); + test(STR("answer is +52"), STR("answer is {:+o}"), CharT('*')); + test(STR("answer is +052"), STR("answer is {:+#o}"), CharT('*')); + test(STR("answer is +2a"), STR("answer is {:+x}"), CharT('*')); + test(STR("answer is +0x2a"), STR("answer is {:+#x}"), CharT('*')); + test(STR("answer is +2A"), STR("answer is {:+X}"), CharT('*')); + test(STR("answer is +0X2A"), STR("answer is {:+#X}"), CharT('*')); + + // *** zero-padding & width *** + test(STR("answer is +00000000042"), STR("answer is {:+#012d}"), CharT('*')); + test(STR("answer is +00000101010"), STR("answer is {:+012b}"), CharT('*')); + test(STR("answer is +0b000101010"), STR("answer is {:+#012b}"), CharT('*')); + test(STR("answer is +0B000101010"), STR("answer is {:+#012B}"), CharT('*')); + test(STR("answer is +00000000052"), STR("answer is {:+012o}"), CharT('*')); + test(STR("answer is +00000000052"), STR("answer is {:+#012o}"), CharT('*')); + test(STR("answer is +0000000002a"), STR("answer is {:+012x}"), CharT('*')); + test(STR("answer is +0x00000002a"), STR("answer is {:+#012x}"), CharT('*')); + test(STR("answer is +0000000002A"), STR("answer is {:+012X}"), CharT('*')); + + test(STR("answer is +0X00000002A"), STR("answer is {:+#012X}"), CharT('*')); + + // *** precision *** + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.d}"), CharT('*')); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0d}"), CharT('*')); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42d}"), CharT('*')); + + // *** locale-specific form *** + // See locale-specific_form.pass.cpp + + // *** type *** + for (const auto& fmt : invalid_types("bBcdoxX")) + test_exception( + "The format-spec type has a type not supported for a char argument", + fmt, '*'); +} + template void test_string(T world, T universe) { @@ -612,6 +760,9 @@ test(STR("hello 09azAZ!"), STR("hello {}{}{}{}{}{}{}"), CharT('0'), CharT('9'), CharT('a'), CharT('z'), CharT('A'), CharT('Z'), CharT('!')); + test_char(); + test_char_as_integer(); + // *** Test string format argument *** { CharT buffer[] = {CharT('0'), CharT('9'), CharT('a'), CharT('z'), diff --git a/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp @@ -0,0 +1,315 @@ +//===----------------------------------------------------------------------===// +// 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 char type. + +#include +#include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +#include +#endif + +#include "concepts_precision.h" +#include "test_macros.h" +#include "make_string.h" + +#define CSTR(S) MAKE_CSTRING(CharT, S) + +using namespace std::__format_spec; + +template +using parser = __parser_char; + +#include "test_exception.h" // requires parser + +template +struct Expected { + CharT fill; + _Flags::_Alignment alignment; + _Flags::_Sign sign; + bool alternate_form; + bool zero_padding; + uint32_t width; + bool width_as_arg; + bool locale_specific_form; + _Flags::_Type type; +}; + +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() { + // *** Align-fill *** + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__char}, + 0, CSTR("}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__char}, + 2, CSTR("-<}")); + test({CharT(' '), _Flags::_Alignment::__center, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__char}, + 1, CSTR("^}")); + test({CharT('+'), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__char}, + 2, CSTR("+>}")); + + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__char}, + 1, CSTR("c}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__char}, + 3, CSTR("-c}")); + + // *** 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("-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({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 1, false, false, _Flags::_Type::__char}, + 1, CSTR("1}")); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 42, false, false, _Flags::_Type::__char}, + 2, CSTR("42}")); + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 2'147'483'647, false, false, _Flags::_Type::__char}, + 10, CSTR("2147483647}")); + + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 1, false, false, _Flags::_Type::__char}, + 2, CSTR("1c}")); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 42, false, false, _Flags::_Type::__char}, + 3, CSTR("42c}")); + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 2'147'483'647, false, false, _Flags::_Type::__char}, + 11, CSTR("2147483647c}")); + + // *** Precision *** + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".")); + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".1")); + + // *** Locale-specific form *** + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, true, _Flags::_Type::__char}, + 1, CSTR("L}")); + + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, true, _Flags::_Type::__char}, + 2, CSTR("Lc}")); +} + +template +constexpr void test_as_integer() { + + // *** Align-fill *** + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR("d}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__decimal}, + 3, CSTR("- +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_as_char(); + test_as_integer(); + + // *** Type *** + { + const char* expected = + "The format-spec type has a type not supported for a char argument"; + test_exception(expected, CSTR("A}")); + test_exception(expected, CSTR("E}")); + test_exception(expected, CSTR("F}")); + test_exception(expected, CSTR("G}")); + test_exception(expected, CSTR("a}")); + test_exception(expected, CSTR("e}")); + test_exception(expected, CSTR("f}")); + test_exception(expected, CSTR("g}")); + test_exception(expected, CSTR("p}")); + test_exception(expected, CSTR("s}")); + } + + // **** General *** + test_exception("The format-spec should consume the input or end with a '}'", + CSTR("ss")); +} + +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) == 2 * sizeof(uint32_t)); + static_assert( + sizeof(parser) == + (sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t))); +#ifndef _LIBCPP_HAS_NO_CHAR8_T + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); + static_assert(sizeof(parser) == 3 * sizeof(uint32_t)); +#endif +#endif + + test(); + static_assert(test()); + + return 0; +}