[libc++][format] Adds bool formatter.

Implements the formatter for Boolean types.
[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
```
  template<> struct formatter<ArithmeticT, charT>;
```
This removes the stub implemented in D96664.

Implements parts of:
- P0645 Text Formatting
- P1652 Printf corner cases in std::format

Completes:
- P1868 width: clarifying units of width and precision in std::format

Reviewed By: #libc, ldionne

Differential Revision: https://reviews.llvm.org/D103670
This commit is contained in:
Mark de Wever 2020-12-14 17:39:15 +01:00
parent 49e736d845
commit 7fb9f99f3b
14 changed files with 918 additions and 36 deletions

View File

@ -171,7 +171,7 @@
"`P1460 <https://wg21.link/P1460>`__","LWG","Mandating the Standard Library: Clause 20 - Utilities library","Prague","* *",""
"`P1739 <https://wg21.link/P1739>`__","LWG","Avoid template bloat for safe_ranges in combination with ""subrange-y"" view adaptors","Prague","* *",""
"`P1831 <https://wg21.link/P1831>`__","LWG","Deprecating volatile: library","Prague","* *",""
"`P1868 <https://wg21.link/P1868>`__","LWG","width: clarifying units of width and precision in std::format","Prague","|In Progress|",""
"`P1868 <https://wg21.link/P1868>`__","LWG","width: clarifying units of width and precision in std::format","Prague","|Complete|","14.0"
"`P1908 <https://wg21.link/P1908>`__","CWG","Reserving Attribute Namespaces for Future Use","Prague","* *",""
"`P1937 <https://wg21.link/P1937>`__","CWG","Fixing inconsistencies between constexpr and consteval functions","Prague","* *",""
"`P1956 <https://wg21.link/P1956>`__","LWG","On the names of low-level bit manipulation functions","Prague","|Complete|","12.0"

1 Paper # Group Paper Name Meeting Status First released version
171 `P1460 <https://wg21.link/P1460>`__ LWG Mandating the Standard Library: Clause 20 - Utilities library Prague * *
172 `P1739 <https://wg21.link/P1739>`__ LWG Avoid template bloat for safe_ranges in combination with "subrange-y" view adaptors Prague * *
173 `P1831 <https://wg21.link/P1831>`__ LWG Deprecating volatile: library Prague * *
174 `P1868 <https://wg21.link/P1868>`__ LWG width: clarifying units of width and precision in std::format Prague |In Progress| |Complete| 14.0
175 `P1908 <https://wg21.link/P1908>`__ CWG Reserving Attribute Namespaces for Future Use Prague * *
176 `P1937 <https://wg21.link/P1937>`__ CWG Fixing inconsistencies between constexpr and consteval functions Prague * *
177 `P1956 <https://wg21.link/P1956>`__ LWG On the names of low-level bit manipulation functions Prague |Complete| 12.0

View File

@ -140,6 +140,7 @@ set(files
__format/format_parse_context.h
__format/format_string.h
__format/formatter.h
__format/formatter_bool.h
__format/formatter_char.h
__format/formatter_integer.h
__format/formatter_integral.h

View File

@ -0,0 +1,145 @@
// -*- 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_BOOL_H
#define _LIBCPP___FORMAT_FORMATTER_BOOL_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>
#include <string_view>
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
#include <locale>
#endif
#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 _CharT>
class _LIBCPP_TEMPLATE_VIS __parser_bool : 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::__string;
[[fallthrough]];
case _Flags::_Type::__string:
this->__handle_bool();
break;
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 bool argument");
}
return __it;
}
};
template <class _CharT>
struct _LIBCPP_TEMPLATE_VIS __bool_strings;
template <>
struct _LIBCPP_TEMPLATE_VIS __bool_strings<char> {
static constexpr string_view __true{"true"};
static constexpr string_view __false{"false"};
};
template <>
struct _LIBCPP_TEMPLATE_VIS __bool_strings<wchar_t> {
static constexpr wstring_view __true{L"true"};
static constexpr wstring_view __false{L"false"};
};
template <class _CharT>
using __formatter_bool = __formatter_integral<__parser_bool<_CharT>>;
} //namespace __format_spec
// [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
template <class _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<bool, _CharT>
: public __format_spec::__formatter_bool<_CharT> {
using _Base = __format_spec::__formatter_bool<_CharT>;
_LIBCPP_HIDE_FROM_ABI auto format(bool __value, auto& __ctx)
-> decltype(__ctx.out()) {
if (this->__type != __format_spec::_Flags::_Type::__string)
return _Base::format(static_cast<unsigned char>(__value), __ctx);
if (this->__width_needs_substitution())
this->__substitute_width_arg_id(__ctx.arg(this->__width));
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
if (this->__locale_specific_form) {
const auto& __np = use_facet<numpunct<_CharT>>(__ctx.locale());
basic_string<_CharT> __str = __value ? __np.truename() : __np.falsename();
return __formatter::__write_unicode(
__ctx.out(), basic_string_view<_CharT>{__str}, this->__width, -1,
this->__fill, this->__alignment);
}
#endif
basic_string_view<_CharT> __str =
__value ? __format_spec::__bool_strings<_CharT>::__true
: __format_spec::__bool_strings<_CharT>::__false;
// The output only uses ASCII so every character is one column.
unsigned __size = __str.size();
if (__size >= this->__width)
return _VSTD::copy(__str.begin(), __str.end(), __ctx.out());
return __formatter::__write(__ctx.out(), __str.begin(), __str.end(), __size,
this->__width, this->__fill, this->__alignment);
}
};
#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
#endif //_LIBCPP_STD_VER > 17
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___FORMAT_FORMATTER_BOOL_H

View File

@ -279,6 +279,7 @@ namespace std {
#include <__format/format_parse_context.h>
#include <__format/format_string.h>
#include <__format/formatter.h>
#include <__format/formatter_bool.h>
#include <__format/formatter_char.h>
#include <__format/formatter_integer.h>
#include <__format/formatter_string.h>
@ -389,28 +390,6 @@ private:
// These specializations are helper stubs and not proper formatters.
// TODO FMT Implement the proper formatter specializations.
// [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
// Boolean.
template <class _CharT>
struct _LIBCPP_TEMPLATE_VIS formatter<bool, _CharT> {
_LIBCPP_HIDE_FROM_ABI
auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) {
// TODO FMT Implement
return __parse_ctx.begin();
}
_LIBCPP_HIDE_FROM_ABI
auto format(bool __b, auto& __ctx) -> decltype(__ctx.out()) {
// TODO FMT Implement using formatting arguments
auto __out_it = __ctx.out();
*__out_it++ = _CharT('0') + __b;
return __out_it;
}
};
// Floating point types.
// TODO FMT There are no replacements for the floating point stubs due to not
// having floating point support in std::to_chars yet. These stubs aren't

View File

@ -450,6 +450,7 @@ module std [system] {
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_bool { private header "__format/formatter_bool.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" }

View File

@ -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_bool.h'}}
#include <__format/formatter_bool.h>

View File

@ -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
// <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 boolean type.
#include <format>
#include <cassert>
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
# include <iostream>
#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 <class CharT>
using Parser = __parser_bool<CharT>;
template <class CharT>
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::__string;
};
template <class CharT>
constexpr void test(Expected<CharT> expected, size_t size,
std::basic_string_view<CharT> fmt) {
// Initialize parser with sufficient arguments to avoid the parsing to fail
// due to insufficient arguments.
std::basic_format_parse_context<CharT> parse_ctx(fmt,
std::__format::__number_max);
auto begin = parse_ctx.begin();
auto end = parse_ctx.end();
Parser<CharT> 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 <class CharT>
constexpr void test(Expected<CharT> 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<CharT> fmt{f};
assert(fmt.back() == CharT('}') && "Pre-condition failure");
test(expected, size, fmt);
fmt.remove_suffix(1);
test(expected, size, fmt);
}
template <class CharT>
constexpr void test_as_string() {
test({}, 1, CSTR("s}"));
// *** 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("<s}"));
test({.alignment = _Flags::_Alignment::__center}, 2, "^s}");
test({.alignment = _Flags::_Alignment::__right}, 2, ">s}");
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("L<s}"));
test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 3,
CSTR("#^s}"));
test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 3,
CSTR("0>s}"));
// *** Sign ***
test_exception<Parser<CharT>>(
"A sign field isn't allowed in this format-spec", CSTR("-}"));
test_exception<Parser<CharT>>(
"A sign field isn't allowed in this format-spec", CSTR("-s}"));
// *** Alternate form ***
test_exception<Parser<CharT>>(
"An alternate form field isn't allowed in this format-spec", CSTR("#}"));
test_exception<Parser<CharT>>(
"An alternate form field isn't allowed in this format-spec", CSTR("#s}"));
// *** Zero padding ***
test_exception<Parser<CharT>>(
"A zero-padding field isn't allowed in this format-spec", CSTR("0}"));
test_exception<Parser<CharT>>(
"A zero-padding field isn't allowed in this format-spec", CSTR("0s}"));
// *** 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<Parser<CharT>>(
"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<Parser<CharT>>(
"The numeric value of the format-spec is too large", CSTR("2147483648"));
test_exception<Parser<CharT>>(
"The numeric value of the format-spec is too large", CSTR("5000000000"));
test_exception<Parser<CharT>>(
"The numeric value of the format-spec is too large", CSTR("10000000000"));
test_exception<Parser<CharT>>("End of input while parsing format-spec arg-id",
CSTR("{"));
test_exception<Parser<CharT>>(
"A format-spec arg-id should terminate at a '}'", CSTR("{0"));
test_exception<Parser<CharT>>(
"The arg-id of the format-spec starts with an invalid character",
CSTR("{a"));
test_exception<Parser<CharT>>(
"A format-spec arg-id should terminate at a '}'", CSTR("{1"));
test_exception<Parser<CharT>>(
"A format-spec arg-id should terminate at a '}'", CSTR("{9"));
test_exception<Parser<CharT>>(
"A format-spec arg-id should terminate at a '}'", CSTR("{9:"));
test_exception<Parser<CharT>>(
"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<Parser<CharT>>(
"The numeric value of the format-spec is too large",
CSTR("{2147483648}"));
test_exception<Parser<CharT>>(
"The numeric value of the format-spec is too large",
CSTR("{5000000000}"));
test_exception<Parser<CharT>>(
"The numeric value of the format-spec is too large",
CSTR("{10000000000}"));
// *** Precision ***
test_exception<Parser<CharT>>(
"The format-spec should consume the input or end with a '}'", CSTR("."));
test_exception<Parser<CharT>>(
"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}, 2, CSTR("Ls}"));
}
template <class CharT>
constexpr void test_as_char() {
test({.type = _Flags::_Type::__char}, 1, CSTR("c}"));
// *** Align-fill ***
test({.alignment = _Flags::_Alignment::__left, .type = _Flags::_Type::__char},
2, CSTR("<c}"));
test({.alignment = _Flags::_Alignment::__center,
.type = _Flags::_Type::__char},
2, "^c}");
test(
{.alignment = _Flags::_Alignment::__right, .type = _Flags::_Type::__char},
2, ">c}");
test({.fill = CharT('L'),
.alignment = _Flags::_Alignment::__left,
.type = _Flags::_Type::__char},
3, CSTR("L<c}"));
test({.fill = CharT('#'),
.alignment = _Flags::_Alignment::__center,
.type = _Flags::_Type::__char},
3, CSTR("#^c}"));
test({.fill = CharT('0'),
.alignment = _Flags::_Alignment::__right,
.type = _Flags::_Type::__char},
3, CSTR("0>c}"));
// *** Sign ***
test_exception<Parser<CharT>>(
"A sign field isn't allowed in this format-spec", CSTR("-c}"));
// *** Alternate form ***
test_exception<Parser<CharT>>(
"An alternate form field isn't allowed in this format-spec", CSTR("#c}"));
// *** Zero padding ***
test_exception<Parser<CharT>>(
"A zero-padding field isn't allowed in this format-spec", CSTR("0c}"));
// *** Width ***
test({.width = 0, .width_as_arg = false, .type = _Flags::_Type::__char}, 1,
CSTR("c}"));
test({.width = 1, .width_as_arg = false, .type = _Flags::_Type::__char}, 2,
CSTR("1c}"));
test({.width = 10, .width_as_arg = false, .type = _Flags::_Type::__char}, 3,
CSTR("10c}"));
test({.width = 1000, .width_as_arg = false, .type = _Flags::_Type::__char}, 5,
CSTR("1000c}"));
test({.width = 1000000, .width_as_arg = false, .type = _Flags::_Type::__char},
8, CSTR("1000000c}"));
test({.width = 0, .width_as_arg = true, .type = _Flags::_Type::__char}, 3,
CSTR("{}c}"));
test({.width = 0, .width_as_arg = true, .type = _Flags::_Type::__char}, 4,
CSTR("{0}c}"));
test({.width = 1, .width_as_arg = true, .type = _Flags::_Type::__char}, 4,
CSTR("{1}c}"));
// *** Precision ***
test_exception<Parser<CharT>>(
"The format-spec should consume the input or end with a '}'", CSTR("."));
test_exception<Parser<CharT>>(
"The format-spec should consume the input or end with a '}'", CSTR(".1"));
// *** Locale-specific form ***
test({.locale_specific_form = true, .type = _Flags::_Type::__char}, 2,
CSTR("Lc}"));
}
template <class CharT>
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({.alignment = _Flags::_Alignment::__center,
.type = _Flags::_Type::__decimal},
2, "^d}");
test({.alignment = _Flags::_Alignment::__right,
.type = _Flags::_Type::__decimal},
2, ">d}");
test({.fill = CharT('L'),
.alignment = _Flags::_Alignment::__left,
.type = _Flags::_Type::__decimal},
3, CSTR("L<d}"));
test({.fill = CharT('#'),
.alignment = _Flags::_Alignment::__center,
.type = _Flags::_Type::__decimal},
3, CSTR("#^d}"));
test({.fill = CharT('0'),
.alignment = _Flags::_Alignment::__right,
.type = _Flags::_Type::__decimal},
3, CSTR("0>d}"));
// *** 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<Parser<CharT>>(
"The format-spec should consume the input or end with a '}'", CSTR("."));
test_exception<Parser<CharT>>(
"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 <class CharT>
constexpr void test() {
Parser<CharT> 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<decltype(parser)>);
static_assert(!has_precision_as_arg<decltype(parser)>);
assert(parser.__locale_specific_form == false);
assert(parser.__type == _Flags::_Type::__default);
test({}, 0, CSTR("}"));
test_as_string<CharT>();
test_as_char<CharT>();
test_as_integer<CharT>();
// *** Type ***
{
const char* expected =
"The format-spec type has a type not supported for a bool argument";
test_exception<Parser<CharT>>(expected, CSTR("A}"));
test_exception<Parser<CharT>>(expected, CSTR("E}"));
test_exception<Parser<CharT>>(expected, CSTR("F}"));
test_exception<Parser<CharT>>(expected, CSTR("G}"));
test_exception<Parser<CharT>>(expected, CSTR("a}"));
test_exception<Parser<CharT>>(expected, CSTR("e}"));
test_exception<Parser<CharT>>(expected, CSTR("f}"));
test_exception<Parser<CharT>>(expected, CSTR("g}"));
test_exception<Parser<CharT>>(expected, CSTR("p}"));
}
// **** General ***
test_exception<Parser<CharT>>(
"The format-spec should consume the input or end with a '}'", CSTR("ss"));
}
constexpr bool test() {
test<char>();
test<wchar_t>();
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<char>) == 2 * sizeof(uint32_t));
static_assert(
sizeof(Parser<wchar_t>) ==
(sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t)));
#endif
test();
static_assert(test());
return 0;
}

View File

@ -67,8 +67,8 @@ void test_termination_condition(StringT expected, StringT f, bool arg) {
template <class CharT>
void test_boolean() {
test_termination_condition(STR("1"), STR("}"), true);
test_termination_condition(STR("0"), STR("}"), false);
test_termination_condition(STR("true"), STR("}"), true);
test_termination_condition(STR("false"), STR("}"), false);
}
int main(int, char**) {

View File

@ -15,6 +15,7 @@
// ExceptionTest must be callable as check_exception(expected-exception, string-to-format, args-to-format...)
#define STR(S) MAKE_STRING(CharT, S)
#define CSTR(S) MAKE_CSTRING(CharT, S)
template <class CharT>
std::vector<std::basic_string<CharT>> invalid_types(std::string valid) {
@ -260,6 +261,238 @@ void format_string_tests(TestFunction check, ExceptionTest check_exception) {
format_test_string_unicode<CharT>(check);
}
template <class CharT, class TestFunction, class ExceptionTest>
void format_test_bool(TestFunction check, ExceptionTest check_exception) {
// *** align-fill & width ***
check(STR("answer is 'true '"), STR("answer is '{:7}'"), true);
check(STR("answer is ' true'"), STR("answer is '{:>7}'"), true);
check(STR("answer is 'true '"), STR("answer is '{:<7}'"), true);
check(STR("answer is ' true '"), STR("answer is '{:^7}'"), true);
check(STR("answer is 'false '"), STR("answer is '{:8s}'"), false);
check(STR("answer is ' false'"), STR("answer is '{:>8s}'"), false);
check(STR("answer is 'false '"), STR("answer is '{:<8s}'"), false);
check(STR("answer is ' false '"), STR("answer is '{:^8s}'"), false);
check(STR("answer is '---true'"), STR("answer is '{:->7}'"), true);
check(STR("answer is 'true---'"), STR("answer is '{:-<7}'"), true);
check(STR("answer is '-true--'"), STR("answer is '{:-^7}'"), true);
check(STR("answer is '---false'"), STR("answer is '{:->8s}'"), false);
check(STR("answer is 'false---'"), STR("answer is '{:-<8s}'"), false);
check(STR("answer is '-false--'"), STR("answer is '{:-^8s}'"), false);
// *** Sign ***
check_exception("A sign field isn't allowed in this format-spec", STR("{:-}"),
true);
check_exception("A sign field isn't allowed in this format-spec", STR("{:+}"),
true);
check_exception("A sign field isn't allowed in this format-spec", STR("{: }"),
true);
check_exception("A sign field isn't allowed in this format-spec",
STR("{:-s}"), true);
check_exception("A sign field isn't allowed in this format-spec",
STR("{:+s}"), true);
check_exception("A sign field isn't allowed in this format-spec",
STR("{: s}"), true);
// *** alternate form ***
check_exception("An alternate form field isn't allowed in this format-spec",
STR("{:#}"), true);
check_exception("An alternate form field isn't allowed in this format-spec",
STR("{:#s}"), true);
// *** zero-padding ***
check_exception("A zero-padding field isn't allowed in this format-spec",
STR("{:0}"), true);
check_exception("A zero-padding field isn't allowed in this format-spec",
STR("{:0s}"), true);
// *** precision ***
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.0}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.42}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.s}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.0s}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.42s}"), true);
// *** locale-specific form ***
// See locale-specific_form.pass.cpp
// *** type ***
for (const auto& fmt : invalid_types<CharT>("bBcdosxX"))
check_exception(
"The format-spec type has a type not supported for a bool argument",
fmt, true);
}
template <class CharT, class TestFunction, class ExceptionTest>
void format_test_bool_as_char(TestFunction check,
ExceptionTest check_exception) {
// *** align-fill & width ***
check(STR("answer is '\1 '"), STR("answer is '{:6c}'"), true);
check(STR("answer is ' \1'"), STR("answer is '{:>6c}'"), true);
check(STR("answer is '\1 '"), STR("answer is '{:<6c}'"), true);
check(STR("answer is ' \1 '"), STR("answer is '{:^6c}'"), true);
check(STR("answer is '-----\1'"), STR("answer is '{:->6c}'"), true);
check(STR("answer is '\1-----'"), STR("answer is '{:-<6c}'"), true);
check(STR("answer is '--\1---'"), STR("answer is '{:-^6c}'"), true);
check(std::basic_string<CharT>(CSTR("answer is '\0 '"), 18),
STR("answer is '{:6c}'"), false);
check(std::basic_string<CharT>(CSTR("answer is '\0 '"), 18),
STR("answer is '{:6c}'"), false);
check(std::basic_string<CharT>(CSTR("answer is ' \0'"), 18),
STR("answer is '{:>6c}'"), false);
check(std::basic_string<CharT>(CSTR("answer is '\0 '"), 18),
STR("answer is '{:<6c}'"), false);
check(std::basic_string<CharT>(CSTR("answer is ' \0 '"), 18),
STR("answer is '{:^6c}'"), false);
check(std::basic_string<CharT>(CSTR("answer is '-----\0'"), 18),
STR("answer is '{:->6c}'"), false);
check(std::basic_string<CharT>(CSTR("answer is '\0-----'"), 18),
STR("answer is '{:-<6c}'"), false);
check(std::basic_string<CharT>(CSTR("answer is '--\0---'"), 18),
STR("answer is '{:-^6c}'"), false);
// *** Sign ***
check_exception("A sign field isn't allowed in this format-spec",
STR("{:-c}"), true);
check_exception("A sign field isn't allowed in this format-spec",
STR("{:+c}"), true);
check_exception("A sign field isn't allowed in this format-spec",
STR("{: c}"), true);
// *** alternate form ***
check_exception("An alternate form field isn't allowed in this format-spec",
STR("{:#c}"), true);
// *** zero-padding ***
check_exception("A zero-padding field isn't allowed in this format-spec",
STR("{:0c}"), true);
// *** precision ***
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.c}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.0c}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.42c}"), true);
// *** locale-specific form ***
// Note it has no effect but it's allowed.
check(STR("answer is '*'"), STR("answer is '{:Lc}'"), '*');
// *** type ***
for (const auto& fmt : invalid_types<CharT>("bBcdosxX"))
check_exception(
"The format-spec type has a type not supported for a bool argument",
fmt, true);
}
template <class CharT, class TestFunction, class ExceptionTest>
void format_test_bool_as_integer(TestFunction check,
ExceptionTest check_exception) {
// *** align-fill & width ***
check(STR("answer is '1'"), STR("answer is '{:<1d}'"), true);
check(STR("answer is '1 '"), STR("answer is '{:<2d}'"), true);
check(STR("answer is '0 '"), STR("answer is '{:<2d}'"), false);
check(STR("answer is ' 1'"), STR("answer is '{:6d}'"), true);
check(STR("answer is ' 1'"), STR("answer is '{:>6d}'"), true);
check(STR("answer is '1 '"), STR("answer is '{:<6d}'"), true);
check(STR("answer is ' 1 '"), STR("answer is '{:^6d}'"), true);
check(STR("answer is '*****0'"), STR("answer is '{:*>6d}'"), false);
check(STR("answer is '0*****'"), STR("answer is '{:*<6d}'"), false);
check(STR("answer is '**0***'"), STR("answer is '{:*^6d}'"), false);
// Test whether zero padding is ignored
check(STR("answer is ' 1'"), STR("answer is '{:>06d}'"), true);
check(STR("answer is '1 '"), STR("answer is '{:<06d}'"), true);
check(STR("answer is ' 1 '"), STR("answer is '{:^06d}'"), true);
// *** Sign ***
check(STR("answer is 1"), STR("answer is {:d}"), true);
check(STR("answer is 0"), STR("answer is {:-d}"), false);
check(STR("answer is +1"), STR("answer is {:+d}"), true);
check(STR("answer is 0"), STR("answer is {: d}"), false);
// *** alternate form ***
check(STR("answer is +1"), STR("answer is {:+#d}"), true);
check(STR("answer is +1"), STR("answer is {:+b}"), true);
check(STR("answer is +0b1"), STR("answer is {:+#b}"), true);
check(STR("answer is +0B1"), STR("answer is {:+#B}"), true);
check(STR("answer is +1"), STR("answer is {:+o}"), true);
check(STR("answer is +01"), STR("answer is {:+#o}"), true);
check(STR("answer is +1"), STR("answer is {:+x}"), true);
check(STR("answer is +0x1"), STR("answer is {:+#x}"), true);
check(STR("answer is +1"), STR("answer is {:+X}"), true);
check(STR("answer is +0X1"), STR("answer is {:+#X}"), true);
check(STR("answer is 0"), STR("answer is {:#d}"), false);
check(STR("answer is 0"), STR("answer is {:b}"), false);
check(STR("answer is 0b0"), STR("answer is {:#b}"), false);
check(STR("answer is 0B0"), STR("answer is {:#B}"), false);
check(STR("answer is 0"), STR("answer is {:o}"), false);
check(STR("answer is 0"), STR("answer is {:#o}"), false);
check(STR("answer is 0"), STR("answer is {:x}"), false);
check(STR("answer is 0x0"), STR("answer is {:#x}"), false);
check(STR("answer is 0"), STR("answer is {:X}"), false);
check(STR("answer is 0X0"), STR("answer is {:#X}"), false);
// *** zero-padding & width ***
check(STR("answer is +00000000001"), STR("answer is {:+#012d}"), true);
check(STR("answer is +00000000001"), STR("answer is {:+012b}"), true);
check(STR("answer is +0b000000001"), STR("answer is {:+#012b}"), true);
check(STR("answer is +0B000000001"), STR("answer is {:+#012B}"), true);
check(STR("answer is +00000000001"), STR("answer is {:+012o}"), true);
check(STR("answer is +00000000001"), STR("answer is {:+#012o}"), true);
check(STR("answer is +00000000001"), STR("answer is {:+012x}"), true);
check(STR("answer is +0x000000001"), STR("answer is {:+#012x}"), true);
check(STR("answer is +00000000001"), STR("answer is {:+012X}"), true);
check(STR("answer is +0X000000001"), STR("answer is {:+#012X}"), true);
check(STR("answer is 000000000000"), STR("answer is {:#012d}"), false);
check(STR("answer is 000000000000"), STR("answer is {:012b}"), false);
check(STR("answer is 0b0000000000"), STR("answer is {:#012b}"), false);
check(STR("answer is 0B0000000000"), STR("answer is {:#012B}"), false);
check(STR("answer is 000000000000"), STR("answer is {:012o}"), false);
check(STR("answer is 000000000000"), STR("answer is {:#012o}"), false);
check(STR("answer is 000000000000"), STR("answer is {:012x}"), false);
check(STR("answer is 0x0000000000"), STR("answer is {:#012x}"), false);
check(STR("answer is 000000000000"), STR("answer is {:012X}"), false);
check(STR("answer is 0X0000000000"), STR("answer is {:#012X}"), false);
// *** precision ***
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.0}"), true);
check_exception("The format-spec should consume the input or end with a '}'",
STR("{:.42}"), true);
// *** locale-specific form ***
// See locale-specific_form.pass.cpp
// *** type ***
for (const auto& fmt : invalid_types<CharT>("bBcdosxX"))
check_exception(
"The format-spec type has a type not supported for a bool argument",
fmt, true);
}
template <class I, class CharT, class TestFunction, class ExceptionTest>
void format_test_integer_as_integer(TestFunction check,
ExceptionTest check_exception) {
@ -743,8 +976,8 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
check(STR("}"), STR("}}"));
// *** Test argument ID ***
check(STR("hello 01"), STR("hello {0:}{1:}"), false, true);
check(STR("hello 10"), STR("hello {1:}{0:}"), false, true);
check(STR("hello false true"), STR("hello {0:} {1:}"), false, true);
check(STR("hello true false"), STR("hello {1:} {0:}"), false, true);
// ** Test invalid format strings ***
check_exception("The format string terminates at a '{'", STR("{"));
@ -799,7 +1032,11 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
format_string_tests<CharT>(check, check_exception);
// *** Test Boolean format argument ***
check(STR("hello 01"), STR("hello {}{}"), false, true);
check(STR("hello false true"), STR("hello {} {}"), false, true);
format_test_bool<CharT>(check, check_exception);
format_test_bool_as_char<CharT>(check, check_exception);
format_test_bool_as_integer<CharT>(check, check_exception);
// *** Test signed integral format argument ***
check(STR("hello 42"), STR("hello {}"), static_cast<signed char>(42));

View File

@ -56,8 +56,8 @@ auto test = []<class CharT, class... Args>(std::basic_string<CharT> expected,
CharT out[4096];
CharT* it = std::format_to(out, std::locale(), fmt, args...);
assert(std::distance(out, it) == int(expected.size()));
*it = '\0';
assert(out == expected);
// Convert to std::string since output contains '\0' for boolean tests.
assert(std::basic_string<CharT>(out, it) == expected);
}
};

View File

@ -57,8 +57,8 @@ auto test = []<class CharT, class... Args>(std::basic_string<CharT> expected,
CharT out[4096];
CharT* it = std::format_to(out, fmt, args...);
assert(std::distance(out, it) == int(expected.size()));
*it = '\0';
assert(out == expected);
// Convert to std::string since output contains '\0' for boolean tests.
assert(std::basic_string<CharT>(out, it) == expected);
}
};

View File

@ -227,6 +227,56 @@ void test(std::basic_string<CharT> expected, std::locale loc,
}
}
#ifndef _LIBCPP_HAS_NO_UNICODE
template <class CharT>
struct numpunct_unicode;
template <>
struct numpunct_unicode<char> : std::numpunct<char> {
string_type do_truename() const override { return "gültig"; }
string_type do_falsename() const override { return "ungültig"; }
};
template <>
struct numpunct_unicode<wchar_t> : std::numpunct<wchar_t> {
string_type do_truename() const override { return L"gültig"; }
string_type do_falsename() const override { return L"ungültig"; }
};
#endif
template <class CharT>
void test_bool() {
std::locale loc = std::locale(std::locale(), new numpunct<CharT>());
std::locale::global(std::locale(LOCALE_en_US_UTF_8));
assert(std::locale().name() == LOCALE_en_US_UTF_8);
test(STR("true"), STR("{:L}"), true);
test(STR("false"), STR("{:L}"), false);
test(STR("yes"), loc, STR("{:L}"), true);
test(STR("no"), loc, STR("{:L}"), false);
std::locale::global(loc);
test(STR("yes"), STR("{:L}"), true);
test(STR("no"), STR("{:L}"), false);
test(STR("true"), std::locale(LOCALE_en_US_UTF_8), STR("{:L}"), true);
test(STR("false"), std::locale(LOCALE_en_US_UTF_8), STR("{:L}"), false);
#ifndef _LIBCPP_HAS_NO_UNICODE
std::locale loc_unicode =
std::locale(std::locale(), new numpunct_unicode<CharT>());
test(STR("gültig"), loc_unicode, STR("{:L}"), true);
test(STR("ungültig"), loc_unicode, STR("{:L}"), false);
test(STR("gültig "), loc_unicode, STR("{:9L}"), true);
test(STR("gültig!!!"), loc_unicode, STR("{:!<9L}"), true);
test(STR("_gültig__"), loc_unicode, STR("{:_^9L}"), true);
test(STR(" gültig"), loc_unicode, STR("{:>9L}"), true);
#endif
}
template <class CharT>
void test_integer() {
std::locale loc = std::locale(std::locale(), new numpunct<CharT>());
@ -557,6 +607,7 @@ void test_integer() {
template <class CharT>
void test() {
test_bool<CharT>();
test_integer<CharT>();
}

View File

@ -68,8 +68,8 @@ auto test = []<class CharT, class... Args>(std::basic_string<CharT> expected,
std::make_format_args<std::basic_format_context<CharT*, CharT>>(
args...));
assert(std::distance(out, it) == int(expected.size()));
*it = '\0';
assert(out == expected);
// Convert to std::string since output contains '\0' for boolean tests.
assert(std::basic_string<CharT>(out, it) == expected);
}
};

View File

@ -71,8 +71,8 @@ auto test = []<class CharT, class... Args>(std::basic_string<CharT> expected,
std::make_format_args<std::basic_format_context<CharT*, CharT>>(
args...));
assert(std::distance(out, it) == int(expected.size()));
*it = '\0';
assert(out == expected);
// Convert to std::string since output contains '\0' for boolean tests.
assert(std::basic_string<CharT>(out, it) == expected);
}
};