diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -202,7 +202,7 @@ "`3233 `__","Broken requirements for ``shared_ptr``\ converting constructors","Prague","","" "`3237 `__","LWG 3038 and 3190 have inconsistent PRs","Prague","","" "`3238 `__","Insufficiently-defined behavior of ``std::function``\ deduction guides","Prague","","" -"`3242 `__","``std::format``\ : missing rules for ``arg-id``\ in ``width``\ and ``precision``\ ","Prague","","" +"`3242 `__","``std::format``\ : missing rules for ``arg-id``\ in ``width``\ and ``precision``\ ","Prague","|Complete|","Clang 14" "`3243 `__","``std::format``\ and negative zeroes","Prague","","" "`3247 `__","``ranges::iter_move``\ should perform ADL-only lookup of ``iter_move``\ ","Prague","","" "`3248 `__","``std::format``\ ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\ presentation types misformat negative numbers","Prague","","" diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -157,7 +157,7 @@ "`P1871 `__","LWG","Should concepts be enabled or disabled?","Belfast","* *","" "`P1872 `__","LWG","span should have size_type, not index_type","Belfast","|Complete|","10.0" "`P1878 `__","LWG","Constraining Readable Types","Belfast","* *","" -"`P1892 `__","LWG","Extended locale-specific presentation specifiers for std::format","Belfast","* *","" +"`P1892 `__","LWG","Extended locale-specific presentation specifiers for std::format","Belfast","|Complete|","14.0" "`P1902 `__","LWG","Missing feature-test macros 2018-2019","Belfast","* *","" "`P1959 `__","LWG","Remove std::weak_equality and std::strong_equality","Belfast","* *","" "`P1960 `__","LWG","NB Comment Changes Reviewed by SG1","Belfast","* *","" @@ -199,4 +199,4 @@ "`P2216R3 `__","LWG",std::format improvements,"June 2021","","" "`P2281R1 `__","LWG",Clarifying range adaptor objects,"June 2021","","" "`P2328R1 `__","LWG",join_view should join all views of ranges,"June 2021","","" -"`P2367R0 `__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","","" \ No newline at end of file +"`P2367R0 `__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","","" 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/parser_std_format_spec.h __function_like.h __functional_base __functional/binary_function.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,6 +13,7 @@ #include <__availability> #include <__config> #include <__format/format_error.h> +#include <__format/parser_std_format_spec.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) #pragma GCC system_header diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -0,0 +1,723 @@ +// -*- 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_PARSER_STD_FORMAT_SPEC_H +#define _LIBCPP___FORMAT_PARSER_STD_FORMAT_SPEC_H + +#include <__config> +#include <__debug> +#include <__format/format_arg.h> +#include <__format/format_error.h> +#include <__format/format_string.h> +#include <__variant/monostate.h> +#include +#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 { + +/** + * Contains the flags for the std-format-spec. + * + * Some format-options can only be used for specific C++types and may depend on + * the selected format-type. + * * The C++type filtering can be done using the proper policies for + * @ref __parser_std. + * * The format-type filtering needs to be done post parsing in the parser + * derived from @ref __parser_std. + */ +class _LIBCPP_TYPE_VIS _Flags { +public: + enum class _LIBCPP_ENUM_VIS _Alignment : uint8_t { + /** + * No alignment is set in the format string. + * + * Zero-padding is ignored when an alignment is selected. + * The default alignment depends on the selected format-type. + */ + __default, + __left, + __center, + __right + }; + enum class _LIBCPP_ENUM_VIS _Sign : uint8_t { + /** + * No sign is set in the format string. + * + * The sign isn't allowed for certain format-types. By using this value + * it's possible to detect whether or not the user explicitly set the sign + * flag. For formatting purposes it behaves the same as @ref __minus. + */ + __default, + __minus, + __plus, + __space + }; + + _Alignment __alignment : 2 {_Alignment::__default}; + _Sign __sign : 2 {_Sign::__default}; + uint8_t __alternate_form : 1 {false}; + uint8_t __zero_padding : 1 {false}; + uint8_t __locale_specific_form : 1 {false}; + + enum class _LIBCPP_ENUM_VIS _Type : uint8_t { + __default, + __string, + __binary_lower_case, + __binary_upper_case, + __octal, + __decimal, + __hexadecimal_lower_case, + __hexadecimal_upper_case, + __pointer, + __char, + __float_hexadecimal_lower_case, + __float_hexadecimal_upper_case, + __scientific_lower_case, + __scientific_upper_case, + __fixed_lower_case, + __fixed_upper_case, + __general_lower_case, + __general_upper_case + }; + + _Type __type{_Type::__default}; +}; + +namespace __detail { +template +_LIBCPP_HIDE_FROM_ABI constexpr bool +__parse_alignment(_CharT __c, _Flags& __flags) noexcept { + switch (__c) { + case _CharT('<'): + __flags.__alignment = _Flags::_Alignment::__left; + return true; + + case _CharT('^'): + __flags.__alignment = _Flags::_Alignment::__center; + return true; + + case _CharT('>'): + __flags.__alignment = _Flags::_Alignment::__right; + return true; + } + return false; +} +} // namespace __detail + +template +class _LIBCPP_TEMPLATE_VIS __parser_fill_align { +public: + // TODO FMT The standard doesn't specify this character is a Unicode + // character. Validate what fmt and MSVC have implemented. + _CharT __fill{_CharT(' ')}; + +protected: + _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* + __parse(const _CharT* __begin, const _CharT* __end, _Flags& __flags) { + _LIBCPP_ASSERT(__begin != __end, "Precondition failure"); + if (__begin + 1 != __end) { + if (__detail::__parse_alignment(*(__begin + 1), __flags)) { + if (*__begin == _CharT('{') || *__begin == _CharT('}')) + __throw_format_error( + "The format-spec fill field contains an invalid character"); + __fill = *__begin; + return __begin + 2; + } + } + + if (__detail::__parse_alignment(*__begin, __flags)) + return __begin + 1; + + return __begin; + } +}; + +template +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* +__parse_sign(const _CharT* __begin, _Flags& __flags) noexcept { + switch (*__begin) { + case _CharT('-'): + __flags.__sign = _Flags::_Sign::__minus; + break; + case _CharT('+'): + __flags.__sign = _Flags::_Sign::__plus; + break; + case _CharT(' '): + __flags.__sign = _Flags::_Sign::__space; + break; + default: + return __begin; + } + return __begin + 1; +} + +template +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* +__parse_alternate_form(const _CharT* __begin, _Flags& __flags) noexcept { + if (*__begin == _CharT('#')) { + __flags.__alternate_form = true; + ++__begin; + } + + return __begin; +} + +template +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* +__parse_zero_padding(const _CharT* __begin, _Flags& __flags) noexcept { + if (*__begin == _CharT('0')) { + __flags.__zero_padding = true; + ++__begin; + } + + return __begin; +} + +template +_LIBCPP_HIDE_FROM_ABI constexpr __format::__parse_number_result< _CharT> +__parse_arg_id(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) { + // This function is a wrapper to call the real parser. But it does the + // validation for the pre-conditions and post-conditions. + if (__begin == __end) + __throw_format_error("End of input while parsing format-spec arg-id"); + + __format::__parse_number_result __r = + __format::__parse_arg_id(__begin, __end, __parse_ctx); + + if (__r.__ptr == __end || *__r.__ptr != _CharT('}')) + __throw_format_error("A format-spec arg-id should terminate at a '}'"); + + ++__r.__ptr; + return __r; +} + +template +_LIBCPP_HIDE_FROM_ABI constexpr uint32_t +__substitute_arg_id(basic_format_arg<_Context> __arg) { + return visit_format_arg( + [](auto __arg) -> uint32_t { + using _Type = decltype(__arg); + if constexpr (integral<_Type>) { + if constexpr (signed_integral<_Type>) { + if (__arg < 0) + __throw_format_error("A format-spec arg-id replacement shouldn't " + "have a negative value"); + } + + using _CT = common_type_t<_Type, decltype(__format::__number_max)>; + if (static_cast<_CT>(__arg) > + 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"); + else + __throw_format_error("A format-spec arg-id replacement argument " + "isn't an integral type"); + }, + __arg); +} + +class _LIBCPP_TYPE_VIS __parser_width { +public: + /** Contains a width or an arg-id. */ + uint32_t __width : 31 {0}; + /** Determines whether the value stored is a width or an arg-id. */ + uint32_t __width_as_arg : 1 {0}; + +protected: + /** + * Does the supplied std-format-spec contain a width field? + * + * When the field isn't present there's no padding required. This can be used + * to optimize the formatting. + */ + constexpr bool __has_width_field() const noexcept { + return __width_as_arg || __width; + } + + /** + * Does the supplied width field contain an arg-id? + * + * If @c true the formatter needs to call @ref __substitute_width_arg_id. + */ + constexpr bool __width_needs_substitution() const noexcept { + return __width_as_arg; + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* + __parse(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) { + if (*__begin == _CharT('0')) + __throw_format_error( + "A format-spec width field shouldn't have a leading zero"); + + if (*__begin == _CharT('{')) { + __format::__parse_number_result __r = + __parse_arg_id(++__begin, __end, __parse_ctx); + __width = __r.__value; + __width_as_arg = 1; + return __r.__ptr; + } + + if (*__begin < _CharT('0') || *__begin > _CharT('9')) + return __begin; + + __format::__parse_number_result __r = + __format::__parse_number(__begin, __end); + __width = __r.__value; + _LIBCPP_ASSERT(__width != 0, + "A zero value isn't allowed and should be impossible, " + "due to validations in this function"); + return __r.__ptr; + } + + void _LIBCPP_HIDE_FROM_ABI constexpr __substitute_width_arg_id(auto __arg) { + _LIBCPP_ASSERT(__width_as_arg == 1, + "Substitute width called when no substitution is required"); + + // The clearing of the flag isn't required but looks better when debugging + // the code. + __width_as_arg = 0; + __width = __substitute_arg_id(__arg); + if (__width == 0) + __throw_format_error( + "A format-spec width field replacement should have a positive value"); + } +}; + +class _LIBCPP_TYPE_VIS __parser_precision { +public: + /** Contains a precision or an arg-id. */ + uint32_t __precision : 31 {__format::__number_max}; + /** + * Determines whether the value stored is a precision or an arg-id. + * + * @note Since @ref __precision == @ref __format::__number_max is a valid + * value, the default value contains an arg-id of INT32_MAX. (This number of + * arguments isn't supported by compilers.) This is used to detect whether + * the std-format-spec contains a precision field. + */ + uint32_t __precision_as_arg : 1 {1}; + +protected: + /** + * Does the supplied std-format-spec contain a precision field? + * + * When the field isn't present there's no truncating required. This can be + * used to optimize the formatting. + */ + constexpr bool __has_precision_field() const noexcept { + + return __precision_as_arg == 0 || // Contains a value? + __precision != __format::__number_max; // The arg-id is valid? + } + + /** + * Does the supplied precision field contain an arg-id? + * + * If @c true the formatter needs to call @ref __substitute_precision_arg_id. + */ + constexpr bool __precision_needs_substitution() const noexcept { + return __precision_as_arg && __precision != __format::__number_max; + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* + __parse(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) { + if (*__begin != _CharT('.')) + return __begin; + + ++__begin; + if (__begin == __end) + __throw_format_error("End of input while parsing format-spec precision"); + + if (*__begin == _CharT('0')) { + ++__begin; + if (__begin != __end && *__begin >= '0' && *__begin <= '9') + __throw_format_error( + "A format-spec precision field shouldn't have a leading zero"); + + __precision = 0; + __precision_as_arg = 0; + return __begin; + } + + if (*__begin == _CharT('{')) { + __format::__parse_number_result __arg_id = + __parse_arg_id(++__begin, __end, __parse_ctx); + _LIBCPP_ASSERT(__arg_id.__value != __format::__number_max, + "Unsupported number of arguments, since this number of " + "arguments is used a special value"); + __precision = __arg_id.__value; + return __arg_id.__ptr; + } + + if (*__begin < _CharT('0') || *__begin > _CharT('9')) + __throw_format_error( + "The format-spec precision field doesn't contain a value or arg-id"); + + __format::__parse_number_result __r = + __format::__parse_number(__begin, __end); + __precision = __r.__value; + __precision_as_arg = 0; + return __r.__ptr; + } + + void _LIBCPP_HIDE_FROM_ABI constexpr __substitute_precision_arg_id( + auto __arg) { + _LIBCPP_ASSERT( + __precision_as_arg == 1 && __precision != __format::__number_max, + "Substitute precision called when no substitution is required"); + + // The clearing of the flag isn't required but looks better when debugging + // the code. + __precision_as_arg = 0; + __precision = __substitute_arg_id(__arg); + } +}; + +template +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* +__parse_locale_specific_form(const _CharT* __begin, _Flags& __flags) noexcept { + if (*__begin == _CharT('L')) { + __flags.__locale_specific_form = true; + ++__begin; + } + + return __begin; +} + +template +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* +__parse_type(const _CharT* __begin, _Flags& __flags) { + + // Determines the type. It does not validate whether the selected type is + // valid. Most formatters have optional fields that are only allowed for + // certain types. These parsers need to do validation after the type has + // been parsed. So its easier to implement the validation for all types in + // the specific parse function. + switch (*__begin) { + case 'A': + __flags.__type = _Flags::_Type::__float_hexadecimal_upper_case; + break; + case 'B': + __flags.__type = _Flags::_Type::__binary_upper_case; + break; + case 'E': + __flags.__type = _Flags::_Type::__scientific_upper_case; + break; + case 'F': + __flags.__type = _Flags::_Type::__fixed_upper_case; + break; + case 'G': + __flags.__type = _Flags::_Type::__general_upper_case; + break; + case 'X': + __flags.__type = _Flags::_Type::__hexadecimal_upper_case; + break; + case 'a': + __flags.__type = _Flags::_Type::__float_hexadecimal_lower_case; + break; + case 'b': + __flags.__type = _Flags::_Type::__binary_lower_case; + break; + case 'c': + __flags.__type = _Flags::_Type::__char; + break; + case 'd': + __flags.__type = _Flags::_Type::__decimal; + break; + case 'e': + __flags.__type = _Flags::_Type::__scientific_lower_case; + break; + case 'f': + __flags.__type = _Flags::_Type::__fixed_lower_case; + break; + case 'g': + __flags.__type = _Flags::_Type::__general_lower_case; + break; + case 'o': + __flags.__type = _Flags::_Type::__octal; + break; + case 'p': + __flags.__type = _Flags::_Type::__pointer; + break; + case 's': + __flags.__type = _Flags::_Type::__string; + break; + case 'x': + __flags.__type = _Flags::_Type::__hexadecimal_lower_case; + break; + default: + return __begin; + } + return ++__begin; +} + +/** + * The parser for the std-format-spec. + * + * [format.string.std]/1 specifies the std-format-spec: + * fill-and-align sign # 0 width precision L type + * + * All these fields are optional. Whether these fields can be used depend on: + * - The type supplied to the format string. + * E.g. A string never uses the sign field so the field may not be set. + * This constrain is validated by the parsers in this file. + * - The supplied value for the optional type field. + * E.g. A int formatted as decimal uses the sign field. + * When formatted as a char the sign field may no longer be set. + * This constrain isn't validated by the parsers in this file. + * + * The base classes are ordered to minimize the amount of padding. + * + * This implements the parser for the string types. + */ +template +class _LIBCPP_TEMPLATE_VIS __parser_string + : public __parser_width, // provides __width(|as_arg) + public __parser_precision, // provides __precision(|as_arg) + public __parser_fill_align<_CharT>, // provides __fill and uses __flags + public _Flags // provides __flags +{ +public: + using char_type = _CharT; + + _LIBCPP_HIDE_FROM_ABI constexpr __parser_string() { + this->__alignment = _Flags::_Alignment::__left; + } + + /** + * The low-level std-format-spec parse function. + * + * @pre __begin points at the beginning of the std-format-spec. This means + * directly after the ':'. + * @pre The std-format-spec parses the entire input, or the first unmatched + * character is a '}'. + * + * @returns The iterator pointing at the last parsed character. + */ + auto _LIBCPP_HIDE_FROM_ABI constexpr parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + auto __it = __parse(__parse_ctx); + __process_display_type(); + return __it; + } + +private: + /** + * Parses the std-format-spec. + * + * @throws __throw_format_error When @a __parse_ctx contains an ill-formed + * std-format-spec. + * + * @returns An iterator to the end of input or point at the closing '}'. + */ + auto _LIBCPP_HIDE_FROM_ABI constexpr __parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + + auto __begin = __parse_ctx.begin(); + auto __end = __parse_ctx.end(); + if (__begin == __end) + return __begin; + + __begin = __parser_fill_align<_CharT>::__parse(__begin, __end, + static_cast<_Flags&>(*this)); + if (__begin == __end) + return __begin; + + __begin = __parser_width::__parse(__begin, __end, __parse_ctx); + if (__begin == __end) + return __begin; + + __begin = __parser_precision::__parse(__begin, __end, __parse_ctx); + if (__begin == __end) + return __begin; + + __begin = __parse_type(__begin, static_cast<_Flags&>(*this)); + + if (__begin != __end && *__begin != _CharT('}')) + __throw_format_error( + "The format-spec should consume the input or end with a '}'"); + + return __begin; + } + + /** Processes the parsed std-format-spec based on the parsed display type. */ + void _LIBCPP_HIDE_FROM_ABI constexpr __process_display_type() { + 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"); + } + } +}; + +/** + * The parser for the std-format-spec. + * + * This implements the parser for the integral types. This includes the + * character type and boolean type. + * + * See @ref __parser_string. + */ +template +class _LIBCPP_TEMPLATE_VIS __parser_integral + : public __parser_width, // provides __width(|as_arg) + public __parser_fill_align<_CharT>, // provides __fill and uses __flags + public _Flags // provides __flags +{ +public: + using char_type = _CharT; + + // TODO FMT This class probably doesn't need public member functions after + // format.string.std/std_format_spec_integral.pass.cpp has been retired. + + /** + * The low-level std-format-spec parse function. + * + * @pre __begin points at the beginning of the std-format-spec. This means + * directly after the ':'. + * @pre The std-format-spec parses the entire input, or the first unmatched + * character is a '}'. + * + * @returns The iterator pointing at the last parsed character. + */ + auto _LIBCPP_HIDE_FROM_ABI constexpr parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + auto __begin = __parse_ctx.begin(); + auto __end = __parse_ctx.end(); + if (__begin == __end) + return __begin; + + __begin = __parser_fill_align<_CharT>::__parse(__begin, __end, + static_cast<_Flags&>(*this)); + if (__begin == __end) + return __begin; + + __begin = __parse_sign(__begin, static_cast<_Flags&>(*this)); + if (__begin == __end) + return __begin; + + __begin = __parse_alternate_form(__begin, static_cast<_Flags&>(*this)); + if (__begin == __end) + return __begin; + + __begin = __parse_zero_padding(__begin, static_cast<_Flags&>(*this)); + if (__begin == __end) + return __begin; + + __begin = __parser_width::__parse(__begin, __end, __parse_ctx); + if (__begin == __end) + return __begin; + + __begin = + __parse_locale_specific_form(__begin, static_cast<_Flags&>(*this)); + if (__begin == __end) + return __begin; + + __begin = __parse_type(__begin, static_cast<_Flags&>(*this)); + + if (__begin != __end && *__begin != _CharT('}')) + __throw_format_error( + "The format-spec should consume the input or end with a '}'"); + + return __begin; + } + +protected: + /** + * Handles the post-parsing updates for the integer types. + * + * Updates the zero-padding and alignment for integer types. + * + * [format.string.std]/13 + * If the 0 character and an align option both appear, the 0 character is + * ignored. + * + * For the formatter a @ref __default alignment means zero-padding. Update + * the alignment based on parsed format string. + */ + _LIBCPP_HIDE_FROM_ABI constexpr void __handle_integer() noexcept { + this->__zero_padding &= this->__alignment == _Flags::_Alignment::__default; + if (!this->__zero_padding && + this->__alignment == _Flags::_Alignment::__default) + this->__alignment = _Flags::_Alignment::__right; + } + + /** + * Handles the post-parsing updates for the character types. + * + * Sets the alignment and validates the format flags set for a character type. + * + * At the moment the validation for a character and a Boolean behave the + * same, but this may change in the future. + */ + _LIBCPP_HIDE_FROM_ABI constexpr void __handle_char() { __handle_bool(); } + + /** + * Handles the post-parsing updates for the Boolean types. + * + * Sets the alignment and validates the format flags set for a Boolean type. + */ + _LIBCPP_HIDE_FROM_ABI constexpr void __handle_bool() { + if (this->__sign != _Flags::_Sign::__default) + __throw_format_error("A sign field isn't allowed in this format-spec"); + + if (this->__alternate_form) + __throw_format_error( + "An alternate form field isn't allowed in this format-spec"); + + if (this->__zero_padding) + __throw_format_error( + "A zero-padding field isn't allowed in this format-spec"); + + if (this->__alignment == _Flags::_Alignment::__default) + this->__alignment = _Flags::_Alignment::__left; + } +}; + +// TODO FMT Add a parser for floating-point values. +// TODO FMT Add a parser for pointer values. + +} // namespace __format_spec + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_PARSER_STD_FORMAT_SPEC_H diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -277,6 +277,7 @@ #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 <__variant/monostate.h> #include diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -431,18 +431,19 @@ export * module __format { - module format_arg { private header "__format/format_arg.h" } - module format_args { private header "__format/format_args.h" } + module format_arg { private header "__format/format_arg.h" } + module format_args { private header "__format/format_args.h" } module format_context { private header "__format/format_context.h" export optional export locale } - module format_error { private header "__format/format_error.h" } - module format_fwd { private header "__format/format_fwd.h" } - 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 format_error { private header "__format/format_error.h" } + module format_fwd { private header "__format/format_fwd.h" } + 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 parser_std_format_spec { private header "__format/parser_std_format_spec.h" } } } module forward_list { diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/parser_std_format_spec.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/parser_std_format_spec.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/parser_std_format_spec.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/parser_std_format_spec.h'}} +#include <__format/parser_std_format_spec.h> diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/concepts_precision.h b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/concepts_precision.h new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/concepts_precision.h @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// 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 LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H +#define LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H + +template +concept has_precision = requires(T parser) { + parser.__precision; +}; + +template +concept has_precision_as_arg = requires(T parser) { + parser.__precision_as_arg; +}; + +#endif // LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp @@ -0,0 +1,305 @@ +//===----------------------------------------------------------------------===// +// 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 string type. + +// TODO FMT This test should removed once the integer parser is implemented. +// The integral specific fields are all tested for the integer, making this +// test redundant. + +#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_integral; + +#include "test_exception.h" // requires parser + +template +struct Expected { + CharT fill = CharT(' '); + _Flags::_Alignment alignment = _Flags::_Alignment::__default; + _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::__default; +}; + +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() { + 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("}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}")); + test({.alignment = _Flags::_Alignment::__center}, 1, "^}"); + test({.alignment = _Flags::_Alignment::__right}, 1, ">}"); + + 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_exception("The format-spec fill field contains an invalid character", + CSTR("{<")); + test_exception("The format-spec fill field contains an invalid character", + CSTR("}<")); + + // *** Sign *** + test({.sign = _Flags::_Sign::__minus}, 1, CSTR("-}")); + test({.sign = _Flags::_Sign::__plus}, 1, CSTR("+}")); + test({.sign = _Flags::_Sign::__space}, 1, CSTR(" }")); + + // *** Alternate form *** + test({.alternate_form = true}, 1, CSTR("#}")); + + // *** Zero padding *** + // TODO FMT What to do with zero-padding without a width? + // [format.string.std]/13 + // A zero (0) character preceding the width field pads the field with + // leading zeros (following any indication of sign or base) to the field + // width, except when applied to an infinity or NaN. + // Obviously it makes no sense, but should it be allowed or is it a format + // errror? + test({.zero_padding = true}, 1, CSTR("0}")); + test({.alignment = _Flags::_Alignment::__center, .zero_padding = true}, 2, + CSTR("^0}")); + + // *** 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 *** + test({.locale_specific_form = true}, 1, CSTR("L}")); + test({.locale_specific_form = true, .type = _Flags::_Type::__decimal}, 2, + CSTR("Ld}")); + test({.locale_specific_form = true, .type = _Flags::_Type::__char}, 2, + CSTR("Lc}")); + + // *** Type *** + + { + const char* not_a_type = + "The format-spec should consume the input or end with a '}'"; + + test({.type = _Flags::_Type::__float_hexadecimal_upper_case}, 1, + CSTR("A}")); + test({.type = _Flags::_Type::__binary_upper_case}, 1, CSTR("B}")); + test_exception(not_a_type, CSTR("C}")); + test_exception(not_a_type, CSTR("D}")); + test({.type = _Flags::_Type::__scientific_upper_case}, 1, CSTR("E}")); + test({.type = _Flags::_Type::__fixed_upper_case}, 1, CSTR("F}")); + test({.type = _Flags::_Type::__general_upper_case}, 1, 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({.type = _Flags::_Type::__hexadecimal_upper_case}, 1, CSTR("X}")); + test_exception(not_a_type, CSTR("Y}")); + test_exception(not_a_type, CSTR("Z}")); + + test({.type = _Flags::_Type::__float_hexadecimal_lower_case}, 1, + CSTR("a}")); + test({.type = _Flags::_Type::__binary_lower_case}, 1, CSTR("b}")); + test({.type = _Flags::_Type::__char}, 1, CSTR("c}")); + test({.type = _Flags::_Type::__decimal}, 1, CSTR("d}")); + test({.type = _Flags::_Type::__scientific_lower_case}, 1, CSTR("e}")); + test({.type = _Flags::_Type::__fixed_lower_case}, 1, CSTR("f}")); + test({.type = _Flags::_Type::__general_lower_case}, 1, 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({.type = _Flags::_Type::__octal}, 1, CSTR("o}")); + test({.type = _Flags::_Type::__pointer}, 1, CSTR("p}")); + test_exception(not_a_type, CSTR("q}")); + test_exception(not_a_type, CSTR("r}")); + test({.type = _Flags::_Type::__string}, 1, 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({.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(); +#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))); +#endif + + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp @@ -0,0 +1,362 @@ +//===----------------------------------------------------------------------===// +// 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 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; + +template +using parser = __parser_string; + +#include "test_exception.h" // requires parser + +template +struct Expected { + CharT fill = CharT(' '); + _Flags::_Alignment alignment = _Flags::_Alignment::__left; + uint32_t width = 0; + bool width_as_arg = false; + uint32_t precision = std::__format::__number_max; + bool precision_as_arg = true; + _Flags::_Type type = _Flags::_Type::__default; +}; + +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 == _Flags::_Sign::__default); + assert(parser.__alternate_form == false); + assert(parser.__zero_padding == false); + assert(parser.__width == expected.width); + assert(parser.__width_as_arg == expected.width_as_arg); + assert(parser.__precision == expected.precision); + assert(parser.__precision_as_arg == expected.precision_as_arg); + assert(parser.__locale_specific_form == false); + 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() { + parser parser; + + assert(parser.__fill == CharT(' ')); + assert(parser.__alignment == _Flags::_Alignment::__left); + 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); + assert(parser.__precision == std::__format::__number_max); + assert(parser.__precision_as_arg == true); + assert(parser.__locale_specific_form == false); + assert(parser.__type == _Flags::_Type::__default); + + test({}, 0, CSTR("}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}")); + test({.alignment = _Flags::_Alignment::__center}, 1, "^}"); + test({.alignment = _Flags::_Alignment::__right}, 1, ">}"); + + 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_exception("The format-spec fill field contains an invalid character", + CSTR("{<")); + test_exception("The format-spec fill field contains an invalid character", + CSTR("}<")); + + // *** Sign *** + 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("-")); + test_exception("The format-spec should consume the input or end with a '}'", + CSTR(" ")); + + // *** Alternate form *** + test_exception("The format-spec should consume the input or end with a '}'", + CSTR("#")); + + // *** Zero padding *** + test_exception("A format-spec width field shouldn't have a leading zero", + CSTR("0")); + + // *** 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({.precision = 0, .precision_as_arg = false}, 2, CSTR(".0}")); + test({.precision = 1, .precision_as_arg = false}, 2, CSTR(".1}")); + test({.precision = 10, .precision_as_arg = false}, 3, CSTR(".10}")); + test({.precision = 1000, .precision_as_arg = false}, 5, CSTR(".1000}")); + test({.precision = 1000000, .precision_as_arg = false}, 8, CSTR(".1000000}")); + + test({.precision = 0, .precision_as_arg = true}, 3, CSTR(".{}}")); + test({.precision = 0, .precision_as_arg = true}, 4, CSTR(".{0}}")); + test({.precision = 1, .precision_as_arg = true}, 4, CSTR(".{1}}")); + + test_exception("A format-spec precision field shouldn't have a leading zero", + CSTR(".00")); + test_exception("A format-spec precision field shouldn't have a leading zero", + CSTR(".01")); + test_exception( + "The format-spec precision field doesn't contain a value or arg-id", + CSTR(".a")); + test_exception( + "The format-spec precision field doesn't contain a value or arg-id", + ".:"); + + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({.precision = 2'147'483'647, .precision_as_arg = false}, 11, + 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({.precision = 2'147'483'646, .precision_as_arg = true}, 13, + 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}")); + + // *** Width & Precision *** + test({.width = 1, + .width_as_arg = false, + .precision = 0, + .precision_as_arg = false}, + 3, CSTR("1.0}")); + test({.width = 0, + .width_as_arg = true, + .precision = 1, + .precision_as_arg = true}, + 5, CSTR("{}.{}}")); + test({.width = 10, + .width_as_arg = true, + .precision = 9, + .precision_as_arg = true}, + 8, CSTR("{10}.{9}}")); + + // *** Locale-specific form *** + test_exception("The format-spec should consume the input or end with a '}'", + CSTR("L")); + + // *** Type *** + + { + const char* unsuported_type = + "The format-spec type has a type not supported for a string argument"; + const char* not_a_type = + "The format-spec should consume the input or end with a '}'"; + + test_exception(unsuported_type, CSTR("A}")); + test_exception(unsuported_type, 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_exception(not_a_type, 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_exception(unsuported_type, CSTR("X}")); + test_exception(not_a_type, CSTR("Y}")); + test_exception(not_a_type, CSTR("Z}")); + + test_exception(unsuported_type, CSTR("a}")); + test_exception(unsuported_type, CSTR("b}")); + test_exception(unsuported_type, CSTR("c}")); + test_exception(unsuported_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_exception(not_a_type, CSTR("l}")); + test_exception(not_a_type, CSTR("m}")); + test_exception(not_a_type, CSTR("n}")); + test_exception(unsuported_type, CSTR("o}")); + test_exception(unsuported_type, CSTR("p}")); + test_exception(not_a_type, CSTR("q}")); + test_exception(not_a_type, CSTR("r}")); + test({.type = _Flags::_Type::__string}, 1, 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_exception(unsuported_type, 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(); +#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. + LIBCPP_STATIC_ASSERT(sizeof(parser) == 3 * sizeof(uint32_t)); + LIBCPP_STATIC_ASSERT( + sizeof(parser) == + (sizeof(wchar_t) <= 2 ? 3 * sizeof(uint32_t) : 4 * sizeof(uint32_t))); +#endif + + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/test_exception.h b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/test_exception.h new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/test_exception.h @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// 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_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_TEST_EXCEPTION_H +#define _LIBCPP_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_TEST_EXCEPTION_H + +// Helper header for the tests in this directory. +// Note the header isn't freestanding. + +namespace detail { +#ifndef TEST_HAS_NO_EXCEPTIONS +template +void test_exception(std::string_view what, const CharT* fmt) { + try { + std::basic_format_parse_context parse_ctx(fmt); + (void)parser{}.parse(parse_ctx); +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + if constexpr (std::same_as) + std::cerr << "\nFormat string " << fmt + << "\nDidn't throw an exception.\n"; +#endif + assert(false); + } catch (std::format_error& e) { +#if defined(_LIBCPP_VERSION) && !defined(_LIBCPP_HAS_NO_LOCALIZATION) + if constexpr (std::same_as) + if (e.what() != what) + std::cerr << "\nFormat string " << fmt << "\nExpected exception " + << what << "\nActual exception " << e.what() << '\n'; +#endif + LIBCPP_ASSERT(e.what() == what); + return; + } + + assert(false); +} +#endif +} // namespace detail + +/** + * Wrapper for the exception tests. + * + * When using the real function directly during in a constexpr test and add + * the `std::is_constant_evaluated()` test there the compilation fails. This + * happens since assert calls the non-constexpr function '__assert_fail'. + * Solve this issue with an layer of indirection. + */ +template +constexpr void test_exception(std::string_view what, const CharT* fmt) { +#ifndef TEST_HAS_NO_EXCEPTIONS + if (!std::is_constant_evaluated()) + detail::test_exception(what, fmt); +#else + (void)what; + (void)fmt; +#endif +} + +#endif // _LIBCPP_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_TEST_EXCEPTION_H