diff --git a/libcudacxx/include/cuda/std/__format/buffer.h b/libcudacxx/include/cuda/std/__format/buffer.h index 11732bed8cb..942be7e34d2 100644 --- a/libcudacxx/include/cuda/std/__format/buffer.h +++ b/libcudacxx/include/cuda/std/__format/buffer.h @@ -33,11 +33,12 @@ #include #include #include +#include #include #include #include #include -#include +#include #include #include #include @@ -540,6 +541,176 @@ using __fmt_buffer_select_t _CCCL_NODEBUG_ALIAS = __fmt_direct_iterator_buffer<_OutIt, _CharT>, __fmt_iterator_buffer<_OutIt, _CharT>>>; +// A dynamically growing buffer intended to be used for retargeting a context. +// +// P2286 Formatting ranges adds range formatting support. It allows the user to +// specify the minimum width for the entire formatted range. The width of the +// range is not known until the range is formatted. Formatting is done to an +// output_iterator so there's no guarantee it would be possible to add the fill +// to the front of the output. Instead the range is formatted to a temporary +// buffer and that buffer is formatted as a string. +// +// There is an issue with that approach, the format context used in +// std::formatter::format contains the output iterator used as part of its +// type. So using this output iterator means there needs to be a new format +// context and the format arguments need to be retargeted to the new context. +// This retargeting is done by a basic_format_context specialized for the +// __iterator of this container. +// +// This class uses its own buffer management, since using vector +// would lead to a circular include with formatter for vector. +template +class __fmt_retarget_buffer +{ + using _Alloc _CCCL_NODEBUG_ALIAS = allocator<_CharT>; + + _CharT* __ptr_; + size_t __capacity_; + size_t __size_{0}; + + _CCCL_API void __grow_buffer() + { + __grow_buffer(__capacity_ * 1.6); + } + + _CCCL_API void __grow_buffer(size_t __capacity) + { + _CCCL_ASSERT(__capacity > __capacity_, "the buffer must grow"); + _Alloc __alloc; + auto __result = ::cuda::std::__allocate_at_least(__alloc, __capacity); + ::cuda::std::copy_n(__ptr_, __size_, __result.ptr); + __alloc.deallocate(__ptr_, __capacity_); + + __ptr_ = __result.ptr; + __capacity_ = __result.count; + } + +public: + using value_type _CCCL_NODEBUG_ALIAS = _CharT; + + struct __iterator + { + using difference_type _CCCL_NODEBUG_ALIAS = ptrdiff_t; + using value_type _CCCL_NODEBUG_ALIAS = _CharT; + + __fmt_retarget_buffer* __buffer_; + + _CCCL_API constexpr explicit __iterator(__fmt_retarget_buffer& __buffer) noexcept + : __buffer_{&__buffer} + {} + + _CCCL_API constexpr __iterator& operator=(const _CharT& __c) + { + __buffer_->push_back(__c); + return *this; + } + + _CCCL_API constexpr __iterator& operator=(_CharT&& __c) + { + __buffer_->push_back(__c); + return *this; + } + + _CCCL_API constexpr __iterator& operator*() noexcept + { + return *this; + } + _CCCL_API constexpr __iterator& operator++() noexcept + { + return *this; + } + _CCCL_API constexpr __iterator operator++(int) noexcept + { + return *this; + } + }; + + _CCCL_API explicit __fmt_retarget_buffer(size_t __size_hint) + { + // When the initial size is very small a lot of resizes happen + // when elements are added. So use a hard-coded minimum size. + // + // Note a size < 2 will not work + // - 0 there is no buffer, while push_back requires 1 empty element. + // - 1 multiplied by the grow factor is 1 and thus the buffer never + // grows. + _Alloc __alloc; + auto __result = ::cuda::std::__allocate_at_least(__alloc, ::cuda::std::max(__size_hint, 256 / sizeof(_CharT))); + __ptr_ = __result.ptr; + __capacity_ = __result.count; + } + + __fmt_retarget_buffer(const __fmt_retarget_buffer&) = delete; + __fmt_retarget_buffer& operator=(const __fmt_retarget_buffer&) = delete; + + _CCCL_API ~__fmt_retarget_buffer() + { + _Alloc{}.deallocate(__ptr_, __capacity_); + } + + [[nodiscard]] _CCCL_API __iterator __make_output_iterator() noexcept + { + return __iterator{*this}; + } + + _CCCL_API void push_back(_CharT __c) + { + __ptr_[__size_++] = __c; + if (__size_ == __capacity_) + { + __grow_buffer(); + } + } + + template + _CCCL_API void __copy(basic_string_view<_InCharT> __str) + { + size_t __n = __str.size(); + if (__size_ + __n >= __capacity_) + { + // Push_back requires the buffer to have room for at least one character. + __grow_buffer(__size_ + __n + 1); + } + + ::cuda::std::copy_n(__str.data(), __n, __ptr_ + __size_); + __size_ += __n; + } + + template + _CCCL_API void __transform(_Iterator __first, _Iterator __last, _UnaryOperation __operation) + { + static_assert(contiguous_iterator<_Iterator>); + _CCCL_ASSERT(__first <= __last, "not a valid range"); + + size_t __n = static_cast(__last - __first); + if (__size_ + __n >= __capacity_) + { + // Push_back requires the buffer to have room for at least one character. + __grow_buffer(__size_ + __n + 1); + } + + ::cuda::std::transform(__first, __last, __ptr_ + __size_, ::cuda::std::move(__operation)); + __size_ += __n; + } + + _CCCL_API void __fill(size_t __n, _CharT __value) + { + if (__size_ + __n >= __capacity_) + { + // Push_back requires the buffer to have room for at least one character. + __grow_buffer(__size_ + __n + 1); + } + + ::cuda::std::fill_n(__ptr_ + __size_, __n, __value); + __size_ += __n; + } + + [[nodiscard]] _CCCL_API constexpr basic_string_view<_CharT> __view() noexcept + { + return {__ptr_, __size_}; + } +}; + _CCCL_END_NAMESPACE_CUDA_STD #include diff --git a/libcudacxx/include/cuda/std/__format/format_context.h b/libcudacxx/include/cuda/std/__format/format_context.h index 64daede7f5a..296a85b8dff 100644 --- a/libcudacxx/include/cuda/std/__format/format_context.h +++ b/libcudacxx/include/cuda/std/__format/format_context.h @@ -20,13 +20,19 @@ # pragma system_header #endif // no system header +#include #include #include +#include +#include +#include #include #include #include #include +#include #include +#include #include #include @@ -77,6 +83,80 @@ class _CCCL_TYPE_VISIBILITY_DEFAULT basic_format_context basic_format_args __args_; }; +// A specialization for __retarget_buffer +// +// See __retarget_buffer for the motivation for this specialization. +// +// This context holds a reference to the instance of the basic_format_context +// that is retargeted. It converts a formatting argument when it is requested +// during formatting. It is expected that the usage of the arguments is rare so +// the lookups are not expected to be used often. An alternative would be to +// convert all elements during construction. +// +// The elements of the retargets context are only used when an underlying +// formatter uses a locale specific formatting or an formatting argument is +// part for the format spec. For example +// format("{:256:{}}", input, 8); +// Here the width of an element in input is determined dynamically. +// Note when the top-level element has no width the retargeting is not needed. +template +class basic_format_context::__iterator, _CharT> +{ + typename __fmt_retarget_buffer<_CharT>::__iterator __out_it_; + void* __ctx_; + basic_format_arg (*__arg_)(void* __ctx, size_t __id); + + template + [[nodiscard]] _CCCL_API static basic_format_arg __arg_impl(void* __ctx, size_t __id) + { + auto __visitor = [&](auto __arg) -> basic_format_arg { + if constexpr (same_as) + { + return {}; + } + else if constexpr (same_as::handle>) + { + // At the moment it's not possible for formatting to use a re-targeted handle. + // TODO FMT add this when support is needed. + ::cuda::std::__throw_format_error("Re-targeting handle not supported"); + } + else + { + return basic_format_arg{ + ::cuda::std::__fmt_determine_arg_t(), + __basic_format_arg_value(__arg)}; + } + }; + return ::cuda::std::visit_format_arg(::cuda::std::move(__visitor), static_cast<_Context*>(__ctx)->arg(__id)); + } + +public: + using iterator = typename __fmt_retarget_buffer<_CharT>::__iterator; + using char_type = _CharT; + template + using formatter_type = formatter<_Tp, _CharT>; + + template + _CCCL_API explicit basic_format_context(iterator __out_it, _Context& __ctx) + : __out_it_{::cuda::std::move(__out_it)} + , __ctx_{::cuda::std::addressof(__ctx)} + , __arg_{__arg_impl<_Context>} + {} + + [[nodiscard]] _CCCL_API basic_format_arg arg(size_t __id) const + { + return __arg_(__ctx_, __id); + } + [[nodiscard]] _CCCL_API iterator out() + { + return ::cuda::std::move(__out_it_); + } + _CCCL_API void advance_to(iterator __it) + { + __out_it_ = ::cuda::std::move(__it); + } +}; + _CCCL_CTAD_SUPPORTED_FOR_TYPE(basic_format_context); template diff --git a/libcudacxx/include/cuda/std/__format/format_spec_parser.h b/libcudacxx/include/cuda/std/__format/format_spec_parser.h index 7089264c6d6..b7617ce7bbd 100644 --- a/libcudacxx/include/cuda/std/__format/format_spec_parser.h +++ b/libcudacxx/include/cuda/std/__format/format_spec_parser.h @@ -227,6 +227,13 @@ struct __fmt_spec_fields __ret.__consume_all_ = true; return __ret; } +[[nodiscard]] _CCCL_HOST_DEVICE_API constexpr __fmt_spec_fields __fmt_spec_fields_tuple() noexcept +{ + __fmt_spec_fields __ret{}; + __ret.__use_range_fill_ = true; + __ret.__clear_brackets_ = true; + return __ret; +} enum class __fmt_spec_alignment : uint8_t { diff --git a/libcudacxx/include/cuda/std/__format/formatter.h b/libcudacxx/include/cuda/std/__format/formatter.h index e49a0d16517..739555b5194 100644 --- a/libcudacxx/include/cuda/std/__format/formatter.h +++ b/libcudacxx/include/cuda/std/__format/formatter.h @@ -20,8 +20,7 @@ # pragma system_header #endif // no system header -#include -#include +#include #include @@ -48,7 +47,7 @@ struct __fmt_disabled_formatter //! - is_move_constructible_v, //! - is_copy_assignable_v, and //! - is_move_assignable_v. -template +template struct _CCCL_TYPE_VISIBILITY_DEFAULT formatter : __fmt_disabled_formatter {}; diff --git a/libcudacxx/include/cuda/std/__format/formatters/tuple.h b/libcudacxx/include/cuda/std/__format/formatters/tuple.h new file mode 100644 index 00000000000..ae34381e1cd --- /dev/null +++ b/libcudacxx/include/cuda/std/__format/formatters/tuple.h @@ -0,0 +1,208 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +#ifndef _CUDA_STD___FORMAT_FORMATERS_TUPLE_H +#define _CUDA_STD___FORMAT_FORMATERS_TUPLE_H + +#include + +#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC) +# pragma GCC system_header +#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG) +# pragma clang system_header +#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC) +# pragma system_header +#endif // no system header + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +_CCCL_BEGIN_NAMESPACE_CUDA_STD + +template +class __fmt_formatter_tuple : public __fmt_disabled_formatter +{}; + +template +[[nodiscard]] _CCCL_API constexpr __fmt_spec_parser<_CharT> __fmt_formatter_tuple_make_parser() noexcept +{ + __fmt_spec_parser<_CharT> __parser{}; + __parser.__alignment_ = ::cuda::std::to_underlying(__fmt_spec_alignment::__left); + return __parser; +} + +template +class __fmt_formatter_tuple<_CharT, + _Tuple, + enable_if_t<__fmt_char_type<_CharT> && __fold_and_v...>>, + _Args...> +{ + tuple, _CharT>...> __underlying_; + basic_string_view<_CharT> __separator_ = _CCCL_STRLIT(_CharT, ", "); + basic_string_view<_CharT> __opening_bracket_ = _CCCL_STRLIT(_CharT, "("); + basic_string_view<_CharT> __closing_bracket_ = _CCCL_STRLIT(_CharT, ")"); + + template + [[nodiscard]] _CCCL_API typename _FormatContext::iterator + __format_tuple(_Tuple2&& __tuple, _FormatContext& __ctx) const + { + __ctx.advance_to(::cuda::std::copy(__opening_bracket_.begin(), __opening_bracket_.end(), __ctx.out())); + + ::cuda::static_for([&](auto __i) { + using ::cuda::std::get; + + if constexpr (__i() > 0) + { + __ctx.advance_to(::cuda::std::copy(__separator_.begin(), __separator_.end(), __ctx.out())); + } + __ctx.advance_to(get<__i()>(__underlying_).format(get<__i()>(__tuple), __ctx)); + }); + + return ::cuda::std::copy(__closing_bracket_.begin(), __closing_bracket_.end(), __ctx.out()); + } + +public: + __fmt_spec_parser<_CharT> __parser_ = ::cuda::std::__fmt_formatter_tuple_make_parser<_CharT>(); + + _CCCL_API constexpr void set_separator(basic_string_view<_CharT> __separator) noexcept + { + __separator_ = __separator; + } + _CCCL_API constexpr void + set_brackets(basic_string_view<_CharT> __opening_bracket, basic_string_view<_CharT> __closing_bracket) noexcept + { + __opening_bracket_ = __opening_bracket; + __closing_bracket_ = __closing_bracket; + } + + template + _CCCL_API constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) + { + auto __begin = __parser_.__parse(__ctx, ::cuda::std::__fmt_spec_fields_tuple()); + auto __end = __ctx.end(); + + // Note 'n' is part of the type here + if (__parser_.__clear_brackets_) + { + set_brackets({}, {}); + } + else if (__begin != __end && *__begin == _CharT{'m'}) + { + if constexpr (sizeof...(_Args) == 2) + { + set_separator(_CCCL_STRLIT(_CharT, ": ")); + set_brackets({}, {}); + ++__begin; + } + else + { + ::cuda::std::__throw_format_error("Type m requires a pair or a tuple with two elements"); + } + } + + if (__begin != __end && *__begin != _CharT{'}'}) + { + ::cuda::std::__throw_format_error("The format specifier should consume the input or end with a '}'"); + } + + __ctx.advance_to(__begin); + + // [format.tuple]/7 + // ... For each element e in underlying_, if e.set_debug_format() + // is a valid expression, calls e.set_debug_format(). + ::cuda::static_for([&](auto __i) { + using ::cuda::std::get; + auto& __formatter = get<__i()>(__underlying_); + __formatter.parse(__ctx); + + // todo(dabayer): Enable setting debug format once we have debug format support. + // Unlike the range_formatter we don't guard against evil parsers. Since + // this format-spec never has a format-spec for the underlying type + // adding the test would give additional overhead. + // ::cuda::std::__set_debug_format(__formatter); + }); + return __begin; + } + + template + typename _FormatContext::iterator _CCCL_API + format(conditional_t<(formattable && ...), const _Tuple&, _Tuple&> __tuple, + _FormatContext& __ctx) const + { + __fmt_parsed_spec<_CharT> __specs = __parser_.__get_parsed_std_spec(__ctx); + + if (!__specs.__has_width()) + { + return __format_tuple(__tuple, __ctx); + } + + // The size of the buffer needed is: + // - open bracket characters + // - close bracket character + // - n elements where every element may have a different size + // - (n -1) separators + // The size of the element is hard to predict, knowing the type helps but + // it depends on the format-spec. As an initial estimate we guess 6 + // characters. + // Typically both brackets are 1 character and the separator is 2 + // characters. Which means there will be + // (n - 1) * 2 + 1 + 1 = n * 2 character + // So estimate 8 times the range size as buffer. + __fmt_retarget_buffer<_CharT> __buffer{8 * tuple_size_v<_Tuple>}; + basic_format_context::__iterator, _CharT> __c{ + __buffer.__make_output_iterator(), __ctx}; + + (void) __format_tuple(__tuple, __c); + return ::cuda::std::__fmt_write_string_no_precision(__buffer.__view(), __ctx.out(), __specs); + } +}; + +template +struct formatter, _CharT> : __fmt_formatter_tuple<_CharT, pair<_Args...>, void, _Args...> +{}; + +template +struct formatter, _CharT> : __fmt_formatter_tuple<_CharT, tuple<_Args...>, void, _Args...> +{}; + +#if _CCCL_HAS_HOST_STD_LIB() +template +struct formatter<::std::pair<_Args...>, _CharT> : __fmt_formatter_tuple<_CharT, ::std::pair<_Args...>, void, _Args...> +{}; + +template +struct formatter<::std::tuple<_Args...>, _CharT> : __fmt_formatter_tuple<_CharT, ::std::tuple<_Args...>, void, _Args...> +{}; +#endif // _CCCL_HAS_HOST_STD_LIB() + +_CCCL_END_NAMESPACE_CUDA_STD + +#include + +#endif // _CUDA_STD___FORMAT_FORMATERS_TUPLE_H diff --git a/libcudacxx/include/cuda/std/__format/output_utils.h b/libcudacxx/include/cuda/std/__format/output_utils.h index 241df9a5f45..b78e468dc92 100644 --- a/libcudacxx/include/cuda/std/__format/output_utils.h +++ b/libcudacxx/include/cuda/std/__format/output_utils.h @@ -106,12 +106,16 @@ _CCCL_END_NV_DIAG_SUPPRESS() template [[nodiscard]] _CCCL_HOST_DEVICE_API _OutIt __fmt_copy(basic_string_view<_CharT> __str, _OutIt __out_it) { - // todo: handle __fmt_retarget_buffer once implemented - if constexpr (same_as>>) + if constexpr (same_as<_OutIt, __back_insert_iterator<__fmt_output_buffer<_OutCharT>>>) { __out_it.__get_container()->__copy(__str); return __out_it; } + else if constexpr (same_as<_OutIt, typename __fmt_retarget_buffer<_OutCharT>::__iterator>) + { + __out_it.__buffer_->__copy(__str); + return __out_it; + } else { return ::cuda::std::copy(__str.begin(), __str.end(), ::cuda::std::move(__out_it)); @@ -137,15 +141,19 @@ template , class _OutCharT = _CharT, [[nodiscard]] _CCCL_HOST_DEVICE_API _OutIt __fmt_transform(_It __first, _It __last, _OutIt __out_it, _UnaryOp __operation) { - // todo: handle __fmt_retarget_buffer once implemented - if constexpr (same_as>>) + if constexpr (same_as<_OutIt, __back_insert_iterator<__fmt_output_buffer<_OutCharT>>>) { __out_it.__get_container()->__transform(__first, __last, ::cuda::std::move(__operation)); return __out_it; } + else if constexpr (same_as<_OutIt, typename __fmt_retarget_buffer<_OutCharT>::__iterator>) + { + __out_it.__buffer_->__transform(__first, __last, ::cuda::std::move(__operation)); + return __out_it; + } else { - return ::cuda::std::transform(__first, __last, ::cuda::std::move(__out_it), __operation); + return ::cuda::std::transform(__first, __last, ::cuda::std::move(__out_it), ::cuda::std::move(__operation)); } } @@ -155,12 +163,16 @@ __fmt_transform(_It __first, _It __last, _OutIt __out_it, _UnaryOp __operation) template [[nodiscard]] _CCCL_HOST_DEVICE_API _OutIt __fmt_fill(_OutIt __out_it, size_t __n, _CharT __value) { - // todo: handle __fmt_retarget_buffer once implemented - if constexpr (same_as>>) + if constexpr (same_as<_OutIt, __back_insert_iterator<__fmt_output_buffer<_CharT>>>) { __out_it.__get_container()->__fill(__n, __value); return __out_it; } + else if constexpr (same_as<_OutIt, typename __fmt_retarget_buffer<_CharT>::__iterator>) + { + __out_it.__buffer_->__fill(__n, __value); + return __out_it; + } else { return ::cuda::std::fill_n(::cuda::std::move(__out_it), __n, __value); @@ -194,8 +206,10 @@ template //! from the type of \a __specs. Integer output uses \c std::to_chars for its //! conversion, which means the [\a __first, \a __last) always contains elements //! of the type \c char. +//! +//! @note We mark this function as _CCCL_NOINLINE_DEVICE to improve ptxas compilation times. template -[[nodiscard]] _CCCL_HOST_DEVICE_API _OutIt +[[nodiscard]] _CCCL_HOST_DEVICE_API _CCCL_NOINLINE_DEVICE _OutIt __fmt_write(basic_string_view<_CharT> __str, _OutIt __out_it, __fmt_parsed_spec<_ParserCharT> __specs, ptrdiff_t __size) { if (__size >= static_cast(__specs.__width_)) diff --git a/libcudacxx/include/cuda/std/__format_ b/libcudacxx/include/cuda/std/__format_ index 8bde8892598..f99f043f4a3 100644 --- a/libcudacxx/include/cuda/std/__format_ +++ b/libcudacxx/include/cuda/std/__format_ @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/libcudacxx/include/cuda/std/__fwd/format.h b/libcudacxx/include/cuda/std/__fwd/format.h index e96561b1684..2c215e4b511 100644 --- a/libcudacxx/include/cuda/std/__fwd/format.h +++ b/libcudacxx/include/cuda/std/__fwd/format.h @@ -103,6 +103,9 @@ using wformat_string = basic_format_string...>; template struct _CCCL_TYPE_VISIBILITY_DEFAULT format_to_n_result; +template +struct _CCCL_TYPE_VISIBILITY_DEFAULT formatter; + enum class range_format { disabled, diff --git a/libcudacxx/include/cuda/std/__string/literal.h b/libcudacxx/include/cuda/std/__string/literal.h new file mode 100644 index 00000000000..d1b4de71ca2 --- /dev/null +++ b/libcudacxx/include/cuda/std/__string/literal.h @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +#ifndef _CUDA_STD___STRING_LITERAL_H +#define _CUDA_STD___STRING_LITERAL_H + +#include + +#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC) +# pragma GCC system_header +#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG) +# pragma clang system_header +#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC) +# pragma system_header +#endif // no system header + +#include +#include +#include + +#include + +_CCCL_BEGIN_NAMESPACE_CUDA_STD + +template +[[nodiscard]] _CCCL_API constexpr const auto& __cccl_select_strlit( + [[maybe_unused]] const _CharStr(&__char_str), + [[maybe_unused]] const _WCharStr(&__wchar_str), +#if _CCCL_HAS_CHAR8_T() + [[maybe_unused]] const _Char8Str(&__char8_str), +#endif // _CCCL_HAS_CHAR8_T() + [[maybe_unused]] const _Char16Str(&__char16_str), + [[maybe_unused]] const _Char32Str(&__char32_str)) noexcept +{ + if constexpr (cuda::std::is_same_v<_CharT, char>) + { + return __char_str; + } + else if constexpr (cuda::std::is_same_v<_CharT, wchar_t>) + { + return __wchar_str; + } +#if _CCCL_HAS_CHAR8_T() + else if constexpr (cuda::std::is_same_v<_CharT, char8_t>) + { + return __char8_str; + } +#endif // _CCCL_HAS_CHAR8_T() + else if constexpr (cuda::std::is_same_v<_CharT, char16_t>) + { + return __char16_str; + } + else if constexpr (cuda::std::is_same_v<_CharT, char32_t>) + { + return __char32_str; + } + else + { + static_assert(cuda::std::__always_false_v<_CharT>, + "Unsupported character type. Supported types are char, wchar_t, char8_t, char16_t, and char32_t."); + _CCCL_UNREACHABLE(); + } +} + +_CCCL_END_NAMESPACE_CUDA_STD + +#if _CCCL_HAS_CHAR8_T() +# define _CCCL_STRLIT(_TYPE, _STR) ::cuda::std::__cccl_select_strlit<_TYPE>(_STR, L##_STR, u8##_STR, u##_STR, U##_STR) +#else // ^^^ _CCCL_HAS_CHAR8_T() ^^^ / vvv !_CCCL_HAS_CHAR8_T() vvv +# define _CCCL_STRLIT(_TYPE, _STR) ::cuda::std::__cccl_select_strlit<_TYPE>(_STR, L##_STR, u##_STR, U##_STR) +#endif // ^^^ !_CCCL_HAS_CHAR8_T() ^^^ + +#include + +#endif // _CUDA_STD___STRING_LITERAL_H diff --git a/libcudacxx/test/libcudacxx/libcxx/literals/str.pass.cpp b/libcudacxx/test/libcudacxx/libcxx/literals/str.pass.cpp new file mode 100644 index 00000000000..6aa978d25bb --- /dev/null +++ b/libcudacxx/test/libcudacxx/libcxx/literals/str.pass.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// Part of libcu++, the C++ Standard Library for your entire system, +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "test_macros.h" + +template +TEST_FUNC constexpr void test() +{ + decltype(auto) str = _CCCL_STRLIT(CharT, "hello"); + static_assert(cuda::std::is_same_v); + + assert(str[0] == CharT{'h'}); + assert(str[1] == CharT{'e'}); + assert(str[2] == CharT{'l'}); + assert(str[3] == CharT{'l'}); + assert(str[4] == CharT{'o'}); + assert(str[5] == CharT{'\0'}); +} + +TEST_FUNC constexpr bool test() +{ + test(); +#if _CCCL_HAS_WCHAR_T() + test(); +#endif // _CCCL_HAS_WCHAR_T() +#if _CCCL_HAS_CHAR8_T() + test(); +#endif // _CCCL_HAS_CHAR8_T() + test(); + test(); + + return true; +} + +int main(int, char**) +{ + test(); + static_assert(test()); + return 0; +} diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.functions/checkers/format_to.h b/libcudacxx/test/libcudacxx/std/text/format/format.functions/checkers/format_to.h index 94b3ae428c7..af23359d758 100644 --- a/libcudacxx/test/libcudacxx/std/text/format/format.functions/checkers/format_to.h +++ b/libcudacxx/test/libcudacxx/std/text/format/format.functions/checkers/format_to.h @@ -36,6 +36,7 @@ check(cuda::std::basic_string_view expected, test_format_string out; cuda::std::format_to(cuda::std::__back_insert_iterator{out}, fmt, cuda::std::forward(args)...); @@ -68,6 +69,7 @@ check(cuda::std::basic_string_view expected, test_format_string expected, test_format_string out; @@ -92,6 +93,7 @@ check(cuda::std::basic_string_view expected, test_format_string expected, cuda::std::basic_string_view return false; } } +#if !defined(TEST_FORMAT_LIMITED_ITERATOR_TESTING) { CharT out[4096]; CharT* it = cuda::std::vformat_to(out, fmt, cuda::std::make_format_args>(args...)); @@ -44,6 +45,7 @@ check(cuda::std::basic_string_view expected, cuda::std::basic_string_view return false; } } +#endif // !TEST_FORMAT_LIMITED_ITERATOR_TESTING) return true; } diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.functions/format_to.tuple.pass.cpp b/libcudacxx/test/libcudacxx/std/text/format/format.functions/format_to.tuple.pass.cpp new file mode 100644 index 00000000000..0b4ec0e9e50 --- /dev/null +++ b/libcudacxx/test/libcudacxx/std/text/format/format.functions/format_to.tuple.pass.cpp @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +// Limit the number of iterators that are being tested per each check(...) call. +// ADDITIONAL_COMPILE_DEFINITIONS: TEST_FORMAT_LIMITED_ITERATOR_TESTING + +// + +// template +// Out format_to(Out out, format-string fmt, const Args&... args); +// template +// Out format_to(Out out, wformat-string fmt, const Args&... args); + +#include "checkers/format_to.h" + +#include "tests/tuple.h" diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.functions/tests/tuple.h b/libcudacxx/test/libcudacxx/std/text/format/format.functions/tests/tuple.h new file mode 100644 index 00000000000..3a8d4d209c4 --- /dev/null +++ b/libcudacxx/test/libcudacxx/std/text/format/format.functions/tests/tuple.h @@ -0,0 +1,410 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#if _CCCL_HAS_HOST_STD_LIB() +# include +# include +#endif // _CCCL_HAS_HOST_STD_LIB() + +#include "format_functions_common.h" +#include "test_macros.h" + +// Provided by the selected checker. +TEST_FUNC bool check(...); +TEST_FUNC bool check_exception(...); + +enum class color +{ + black, + red, + gold +}; + +template +struct cuda::std::formatter : cuda::std::formatter, CharT> +{ + template + TEST_FUNC auto format(color c, Ctx& ctx) const + { + constexpr basic_string_view color_names[]{ + TEST_STRLIT(CharT, "black"), TEST_STRLIT(CharT, "red"), TEST_STRLIT(CharT, "gold")}; + return formatter, CharT>::format(color_names[static_cast(c)], ctx); + } +}; + +// +// Generic tests for a tuple and pair with two elements. +// +template +TEST_FUNC void test_tuple_or_pair_int_int(TupleOrPair&& input) +{ + assert(check(SV("(42, 99)"), SV("{}"), input)); + assert(check(SV("(42, 99)^42"), SV("{}^42"), input)); + assert(check(SV("(42, 99)^42"), SV("{:}^42"), input)); + + // *** align-fill & width *** + assert(check(SV("(42, 99) "), SV("{:13}"), input)); + assert(check(SV("(42, 99)*****"), SV("{:*<13}"), input)); + assert(check(SV("__(42, 99)___"), SV("{:_^13}"), input)); + assert(check(SV("#####(42, 99)"), SV("{:#>13}"), input)); + + assert(check(SV("(42, 99) "), SV("{:{}}"), input, 13)); + assert(check(SV("(42, 99)*****"), SV("{:*<{}}"), input, 13)); + assert(check(SV("__(42, 99)___"), SV("{:_^{}}"), input, 13)); + assert(check(SV("#####(42, 99)"), SV("{:#>{}}"), input, 13)); + + assert(check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input)); + assert(check_exception("The fill option contains an invalid value", SV("{:{<}"), input)); + + // *** sign *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input)); + + // *** alternate form *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input)); + + // *** zero-padding *** + assert(check_exception("The width option should not have a leading zero", SV("{:0}"), input)); + + // *** precision *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input)); + + // *** type *** + assert(check(SV("__42: 99___"), SV("{:_^11m}"), input)); + assert(check(SV("__42, 99___"), SV("{:_^11n}"), input)); + + // todo(dabayer): Test invalid specifiers. + // for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) { + // assert(check_exception("The format specifier should consume the input or end with a '}'", + // cuda::std::basic_string_view{STR("{:") + c + STR("}")}, + // input)); + // } +} + +template +TEST_FUNC void test_tuple_or_pair_int_string(TupleOrPair&& input) +{ + assert(check(SV("(42, \"hello\")"), SV("{}"), input)); + assert(check(SV("(42, \"hello\")^42"), SV("{}^42"), input)); + assert(check(SV("(42, \"hello\")^42"), SV("{:}^42"), input)); + + // *** align-fill & width *** + assert(check(SV("(42, \"hello\") "), SV("{:18}"), input)); + assert(check(SV("(42, \"hello\")*****"), SV("{:*<18}"), input)); + assert(check(SV("__(42, \"hello\")___"), SV("{:_^18}"), input)); + assert(check(SV("#####(42, \"hello\")"), SV("{:#>18}"), input)); + + assert(check(SV("(42, \"hello\") "), SV("{:{}}"), input, 18)); + assert(check(SV("(42, \"hello\")*****"), SV("{:*<{}}"), input, 18)); + assert(check(SV("__(42, \"hello\")___"), SV("{:_^{}}"), input, 18)); + assert(check(SV("#####(42, \"hello\")"), SV("{:#>{}}"), input, 18)); + + assert(check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input)); + assert(check_exception("The fill option contains an invalid value", SV("{:{<}"), input)); + + // *** sign *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input)); + + // *** alternate form *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input)); + + // *** zero-padding *** + assert(check_exception("The width option should not have a leading zero", SV("{:0}"), input)); + + // *** precision *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input)); + + // *** type *** + assert(check(SV("__42: \"hello\"___"), SV("{:_^16m}"), input)); + assert(check(SV("__42, \"hello\"___"), SV("{:_^16n}"), input)); + + // todo(dabayer): Test invalid specifiers. + // for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) { + // assert(check_exception("The format specifier should consume the input or end with a '}'", + // cuda::std::basic_string_view{STR("{:") + c + STR("}")}, + // input)); + // } +} + +template +TEST_FUNC void test_escaping(TupleOrPair&& input) +{ + static_assert(cuda::std::same_as(input))>, CharT>); + static_assert(cuda::std::same_as(input))>, + cuda::std::basic_string_view>); + + assert(check(SV(R"(('*', ""))"), SV("{}"), input)); + + // Char + cuda::std::get<0>(input) = CharT('\t'); + assert(check(SV(R"(('\t', ""))"), SV("{}"), input)); + cuda::std::get<0>(input) = CharT('\n'); + assert(check(SV(R"(('\n', ""))"), SV("{}"), input)); + cuda::std::get<0>(input) = CharT('\0'); + assert(check(SV(R"(('\u{0}', ""))"), SV("{}"), input)); + + // String + cuda::std::get<0>(input) = CharT('*'); + cuda::std::get<1>(input) = SV("hellö"); + assert(check(SV("('*', \"hellö\")"), SV("{}"), input)); +} + +// +// pair tests +// + +template +TEST_FUNC void test_pair_int_int() +{ + test_tuple_or_pair_int_int(cuda::std::make_pair(42, 99)); +} + +template +TEST_FUNC void test_pair_int_string() +{ + test_tuple_or_pair_int_string(cuda::std::make_pair(42, SV("hello"))); + test_tuple_or_pair_int_string(cuda::std::make_pair(42, CSTR("hello"))); +} + +// +// tuple tests +// + +template +TEST_FUNC void test_tuple_int() +{ + auto input = cuda::std::make_tuple(42); + + assert(check(SV("(42)"), SV("{}"), input)); + assert(check(SV("(42)^42"), SV("{}^42"), input)); + assert(check(SV("(42)^42"), SV("{:}^42"), input)); + + // *** align-fill & width *** + assert(check(SV("(42) "), SV("{:9}"), input)); + assert(check(SV("(42)*****"), SV("{:*<9}"), input)); + assert(check(SV("__(42)___"), SV("{:_^9}"), input)); + assert(check(SV("#####(42)"), SV("{:#>9}"), input)); + + assert(check(SV("(42) "), SV("{:{}}"), input, 9)); + assert(check(SV("(42)*****"), SV("{:*<{}}"), input, 9)); + assert(check(SV("__(42)___"), SV("{:_^{}}"), input, 9)); + assert(check(SV("#####(42)"), SV("{:#>{}}"), input, 9)); + + assert(check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input)); + assert(check_exception("The fill option contains an invalid value", SV("{:{<}"), input)); + + // *** sign *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input)); + + // *** alternate form *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input)); + + // *** zero-padding *** + assert(check_exception("The width option should not have a leading zero", SV("{:0}"), input)); + + // *** precision *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input)); + + // *** type *** + assert(check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input)); + assert(check(SV("__42___"), SV("{:_^7n}"), input)); + + // todo(dabayer): Test invalid specifiers. + // for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) { + // check_exception("The format specifier should consume the input or end with a '}'", + // cuda::std::basic_string_view{STR("{:") + c + STR("}")}, + // input); + // } +} + +template +TEST_FUNC void test_tuple_int_string_color() +{ + const auto input = cuda::std::make_tuple(42, SV("hello"), color::red); + + assert(check(SV("(42, \"hello\", \"red\")"), SV("{}"), input)); + assert(check(SV("(42, \"hello\", \"red\")^42"), SV("{}^42"), input)); + assert(check(SV("(42, \"hello\", \"red\")^42"), SV("{:}^42"), input)); + + // *** align-fill & width *** + assert(check(SV("(42, \"hello\", \"red\") "), SV("{:25}"), input)); + assert(check(SV("(42, \"hello\", \"red\")*****"), SV("{:*<25}"), input)); + assert(check(SV("__(42, \"hello\", \"red\")___"), SV("{:_^25}"), input)); + assert(check(SV("#####(42, \"hello\", \"red\")"), SV("{:#>25}"), input)); + + assert(check(SV("(42, \"hello\", \"red\") "), SV("{:{}}"), input, 25)); + assert(check(SV("(42, \"hello\", \"red\")*****"), SV("{:*<{}}"), input, 25)); + assert(check(SV("__(42, \"hello\", \"red\")___"), SV("{:_^{}}"), input, 25)); + assert(check(SV("#####(42, \"hello\", \"red\")"), SV("{:#>{}}"), input, 25)); + + assert(check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input)); + assert(check_exception("The fill option contains an invalid value", SV("{:{<}"), input)); + + // *** sign *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input)); + + // *** alternate form *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input)); + + // *** zero-padding *** + assert(check_exception("The width option should not have a leading zero", SV("{:0}"), input)); + + // *** precision *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input)); + + // *** type *** + assert(check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input)); + assert(check(SV("__42, \"hello\", \"red\"___"), SV("{:_^23n}"), input)); + + // todo(dabayer): Test invalid specifiers. + // for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) { + // assert(check_exception("The format specifier should consume the input or end with a '}'", + // cuda::std::basic_string_view{STR("{:") + c + STR("}")}, + // input)); + // } +} + +template +TEST_FUNC void test_tuple_int_int() +{ + test_tuple_or_pair_int_int(cuda::std::make_tuple(42, 99)); +} + +template +TEST_FUNC void test_tuple_int_string() +{ + test_tuple_or_pair_int_string(cuda::std::make_tuple(42, SV("hello"))); + test_tuple_or_pair_int_string(cuda::std::make_tuple(42, CSTR("hello"))); +} + +// +// nested tests +// + +template +TEST_FUNC void test_nested(Nested&& input) +{ + // [format.formatter.spec]/2 + // A debug-enabled specialization of formatter additionally provides a + // public, constexpr, non-static member function set_debug_format() + // which modifies the state of the formatter to be as if the type of the + // std-format-spec parsed by the last call to parse were ?. + // pair and tuple are not debug-enabled specializations to the + // set_debug_format is not propagated. The paper + // P2733 Fix handling of empty specifiers in cuda::std::format + // addressed this. + + assert(check(SV("(42, (\"hello\", \"red\"))"), SV("{}"), input)); + assert(check(SV("(42, (\"hello\", \"red\"))^42"), SV("{}^42"), input)); + assert(check(SV("(42, (\"hello\", \"red\"))^42"), SV("{:}^42"), input)); + + // *** align-fill & width *** + assert(check(SV("(42, (\"hello\", \"red\")) "), SV("{:27}"), input)); + assert(check(SV("(42, (\"hello\", \"red\"))*****"), SV("{:*<27}"), input)); + assert(check(SV("__(42, (\"hello\", \"red\"))___"), SV("{:_^27}"), input)); + assert(check(SV("#####(42, (\"hello\", \"red\"))"), SV("{:#>27}"), input)); + + assert(check(SV("(42, (\"hello\", \"red\")) "), SV("{:{}}"), input, 27)); + assert(check(SV("(42, (\"hello\", \"red\"))*****"), SV("{:*<{}}"), input, 27)); + assert(check(SV("__(42, (\"hello\", \"red\"))___"), SV("{:_^{}}"), input, 27)); + assert(check(SV("#####(42, (\"hello\", \"red\"))"), SV("{:#>{}}"), input, 27)); + + assert(check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input)); + assert(check_exception("The fill option contains an invalid value", SV("{:{<}"), input)); + + // *** sign *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input)); + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input)); + + // *** alternate form *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input)); + + // *** zero-padding *** + assert(check_exception("The width option should not have a leading zero", SV("{:0}"), input)); + + // *** precision *** + assert(check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input)); + + // *** type *** + assert(check(SV("__42: (\"hello\", \"red\")___"), SV("{:_^25m}"), input)); + assert(check(SV("__42, (\"hello\", \"red\")___"), SV("{:_^25n}"), input)); + + // todo(dabayer): Test invalid specifiers. + // for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) { + // assert(check_exception("The format specifier should consume the input or end with a '}'", + // cuda::std::basic_string_view{STR("{:") + c + STR("}")}, + // input)); + // } +} + +template +TEST_FUNC void test() +{ + // todo(dabayer): Enable tuple with strings tests once debug formatting is implemented. + + test_pair_int_int(); + // test_pair_int_string(); + + test_tuple_int(); + test_tuple_int_int(); + // test_tuple_int_string(); + // test_tuple_int_string_color(); + + // test_nested(cuda::std::make_pair(42, cuda::std::make_pair(SV("hello"), color::red))); + // test_nested(cuda::std::make_pair(42, cuda::std::make_tuple(SV("hello"), color::red))); + // test_nested(cuda::std::make_tuple(42, cuda::std::make_pair(SV("hello"), color::red))); + // test_nested(cuda::std::make_tuple(42, cuda::std::make_tuple(SV("hello"), color::red))); + + // test_escaping(cuda::std::make_pair(CharT('*'), SV(""))); + // test_escaping(cuda::std::make_tuple(CharT('*'), SV(""))); + + // Test const ref-qualified types. + assert(check(SV("(42)"), SV("{}"), cuda::std::tuple{42})); + assert(check(SV("(42)"), SV("{}"), cuda::std::tuple{42})); + + int answer = 42; + assert(check(SV("(42)"), SV("{}"), cuda::std::tuple{answer})); + assert(check(SV("(42)"), SV("{}"), cuda::std::tuple{answer})); + + assert(check(SV("(42)"), SV("{}"), cuda::std::tuple{42})); + assert(check(SV("(42)"), SV("{}"), cuda::std::tuple{42})); + +#if _CCCL_HAS_HOST_STD_LIB() + NV_IF_TARGET(NV_IS_HOST, ({ + assert(check(SV("(42, true)"), SV("{}"), std::tuple{42, true})); + assert(check(SV("(42, true)"), SV("{}"), std::pair{42, true})); + })) +#endif // _CCCL_HAS_HOST_STD_LIB() +} + +TEST_FUNC void test() +{ + test(); +#if _CCCL_HAS_WCHAR_T() + test(); +#endif // _CCCL_HAS_WCHAR_T() +} + +int main(int, char**) +{ + test(); + return 0; +} diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.functions/vformat_to.tuple.pass.cpp b/libcudacxx/test/libcudacxx/std/text/format/format.functions/vformat_to.tuple.pass.cpp new file mode 100644 index 00000000000..e53dd555dbe --- /dev/null +++ b/libcudacxx/test/libcudacxx/std/text/format/format.functions/vformat_to.tuple.pass.cpp @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: enable-tile +// error: bit field read/write is unsupported in tile code + +// Limit the number of iterators that are being tested per each check(...) call. +// ADDITIONAL_COMPILE_DEFINITIONS: TEST_FORMAT_LIMITED_ITERATOR_TESTING + +// + +// template +// Out vformat_to(Out out, string_view fmt, format_args args); +// template +// Out vformat_to(Out out, wstring_view fmt, wformat_args_t args); + +#include "checkers/vformat_to.h" + +#include "tests/tuple.h" diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.tuple/format.pass.cpp b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/format.pass.cpp new file mode 100644 index 00000000000..bd8796f2123 --- /dev/null +++ b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/format.pass.cpp @@ -0,0 +1,151 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +// + +// template... Ts> +// struct formatter, charT> + +// template +// typename FormatContext::iterator +// format(see below& elems, FormatContext& ctx) const; + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include +#include +#include +#include +#include +#include + +#if _CCCL_HAS_HOST_STD_LIB() +# include +# include +#endif // _CCCL_HAS_HOST_STD_LIB() + +#include "format_functions_common.h" +#include "literal.h" +#include "test_macros.h" + +template +TEST_FUNC void test(StringViewT expected, Arg arg) +{ + using CharT = typename StringViewT::value_type; + using Container = cuda::std::inplace_vector; + using OutIt = cuda::std::__back_insert_iterator; + using FormatCtxT = cuda::std::basic_format_context; + + const cuda::std::formatter formatter; + + Container result; + OutIt out = cuda::std::__back_insert_iterator{result}; + FormatCtxT format_ctx = + cuda::std::__fmt_make_format_context(out, cuda::std::make_format_args(arg)); + formatter.format(arg, format_ctx); + assert(StringViewT{result} == expected); +} + +template +TEST_FUNC void test_assure_parse_is_called(cuda::std::basic_string_view fmt) +{ + using Container = cuda::std::inplace_vector; + using OutIt = cuda::std::__back_insert_iterator; + using FormatCtxT = cuda::std::basic_format_context; + cuda::std::pair arg; + + Container result; + OutIt out = cuda::std::__back_insert_iterator{result}; + FormatCtxT format_ctx = + cuda::std::__fmt_make_format_context(out, cuda::std::make_format_args(arg)); + + cuda::std::formatter formatter; + cuda::std::basic_format_parse_context ctx{fmt}; + + formatter.parse(ctx); + formatter.format(arg, format_ctx); +} + +template +TEST_FUNC void test_assure_parse_is_called() +{ + using Container = cuda::std::inplace_vector; + using OutIt = cuda::std::__back_insert_iterator; + using FormatCtxT = cuda::std::basic_format_context; + cuda::std::pair arg; + + Container result; + OutIt out = cuda::std::__back_insert_iterator{result}; + [[maybe_unused]] FormatCtxT format_ctx = + cuda::std::__fmt_make_format_context(out, cuda::std::make_format_args(arg)); + +#if _CCCL_HAS_EXCEPTIONS() + NV_IF_TARGET(NV_IS_HOST, + ({ // parse not called + [[maybe_unused]] const cuda::std::formatter formatter; + try + { + formatter.format(arg, format_ctx); + assert(false); + } + catch (const parse_call_validator::parse_function_not_called&) + { + assert(true); + } + catch (...) + { + assert(false); + } + })) +#endif // _CCCL_HAS_EXCEPTIONS() + + test_assure_parse_is_called(cuda::std::basic_string_view{TEST_STRLIT(CharT, "5")}); + test_assure_parse_is_called(cuda::std::basic_string_view{TEST_STRLIT(CharT, "n")}); + test_assure_parse_is_called(cuda::std::basic_string_view{TEST_STRLIT(CharT, "m")}); + test_assure_parse_is_called(cuda::std::basic_string_view{TEST_STRLIT(CharT, "5n")}); + test_assure_parse_is_called(cuda::std::basic_string_view{TEST_STRLIT(CharT, "5m")}); +} + +template +TEST_FUNC void test() +{ + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "(1)")}, cuda::std::tuple{1}); + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "(1, 1)")}, cuda::std::tuple{1, CharT{'1'}}); + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "(1, 1)")}, cuda::std::pair{1, CharT{'1'}}); + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "(1, 1, true)")}, + cuda::std::tuple{1, CharT{'1'}, true}); + +#if _CCCL_HAS_HOST_STD_LIB() + NV_IF_TARGET(NV_IS_HOST, ({ + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "(1, 1)")}, + std::tuple{1, CharT{'1'}}); + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "(1, 1)")}, std::pair{1, CharT{'1'}}); + })) +#endif // _CCCL_HAS_HOST_STD_LIB() + + test_assure_parse_is_called(); +} + +TEST_FUNC void test() +{ + test(); +#if _CCCL_HAS_WCHAR_T() + test(); +#endif // _CCCL_HAS_WCHAR_T() +} + +int main(int, char**) +{ + test(); + + return 0; +} diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.tuple/parse.pass.cpp b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/parse.pass.cpp new file mode 100644 index 00000000000..3571ef48e93 --- /dev/null +++ b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/parse.pass.cpp @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +// + +// template... Ts> +// struct formatter, charT> + +// template +// constexpr typename ParseContext::iterator +// parse(ParseContext& ctx); + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include +#include +#include +#include +#include +#include + +#if _CCCL_HAS_HOST_STD_LIB() +# include +# include +#endif // _CCCL_HAS_HOST_STD_LIB() + +#include "literal.h" +#include "test_macros.h" + +template +TEST_FUNC constexpr void test(StringViewT fmt, cuda::std::size_t offset) +{ + using CharT = typename StringViewT::value_type; + + cuda::std::basic_format_parse_context parse_ctx{fmt}; + cuda::std::formatter formatter; + + static_assert(cuda::std::semiregular); + + static_assert(cuda::std::is_same_v); + static_assert(!noexcept(formatter.parse(parse_ctx))); + + auto it = formatter.parse(parse_ctx); + // cuda::std::to_address works around LWG3989 and MSVC STL's iterator debugging mechanism. + assert(cuda::std::to_address(it) == cuda::std::to_address(fmt.end()) - offset); +} + +template +TEST_FUNC constexpr void test() +{ + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "")}, 0); + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "42")}, 0); + + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "}")}, 1); + test(cuda::std::basic_string_view{TEST_STRLIT(CharT, "42}")}, 1); +} + +template +TEST_FUNC constexpr void test() +{ + test>(); + test>(); + test>(); + test>(); + +#if _CCCL_HAS_HOST_STD_LIB() + NV_IF_TARGET(NV_IS_HOST, ({ + test>(); + test>(); + })) +#endif // _CCCL_HAS_HOST_STD_LIB() +} + +TEST_FUNC constexpr bool test() +{ + test(); +#if _CCCL_HAS_WCHAR_T() + test(); +#endif // _CCCL_HAS_WCHAR_T() + + return true; +} + +int main(int, char**) +{ + test(); + static_assert(test()); + + return 0; +} diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.tuple/set_brackets.pass.cpp b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/set_brackets.pass.cpp new file mode 100644 index 00000000000..597c42cea9c --- /dev/null +++ b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/set_brackets.pass.cpp @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +// + +// template... Ts> +// struct formatter, charT> + +// constexpr void constexpr void set_brackets(basic_string_view opening, +// basic_string_view closing) noexcept; + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include +#include + +#if _CCCL_HAS_HOST_STD_LIB() +# include +# include +#endif // _CCCL_HAS_HOST_STD_LIB() + +#include "literal.h" +#include "test_macros.h" + +template +TEST_FUNC constexpr void test() +{ + cuda::std::formatter formatter; + + static_assert( + cuda::std::is_same_v< + void, + decltype(formatter.set_brackets(cuda::std::basic_string_view{}, cuda::std::basic_string_view{}))>); + static_assert( + noexcept(formatter.set_brackets(cuda::std::basic_string_view{}, cuda::std::basic_string_view{}))); + + formatter.set_brackets(cuda::std::basic_string_view{TEST_STRLIT(CharT, "open")}, + cuda::std::basic_string_view{TEST_STRLIT(CharT, "close")}); + + // Note there is no direct way to validate this function modified the object. +} + +template +TEST_FUNC constexpr void test() +{ + test>(); + test>(); + test>(); + test>(); + +#if _CCCL_HAS_HOST_STD_LIB() + NV_IF_TARGET(NV_IS_HOST, ({ + test>(); + test>(); + })) +#endif // _CCCL_HAS_HOST_STD_LIB() +} + +TEST_FUNC constexpr bool test() +{ + test(); +#if _CCCL_HAS_WCHAR_T() + test(); +#endif // _CCCL_HAS_WCHAR_T() + + return true; +} + +int main(int, char**) +{ + test(); + static_assert(test()); + + return 0; +} diff --git a/libcudacxx/test/libcudacxx/std/text/format/format.tuple/set_separator.pass.cpp b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/set_separator.pass.cpp new file mode 100644 index 00000000000..e294715fba6 --- /dev/null +++ b/libcudacxx/test/libcudacxx/std/text/format/format.tuple/set_separator.pass.cpp @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +// + +// class range_formatter +// template... Ts> +// struct formatter, charT> + +// constexpr void set_separator(basic_string_view sep) noexcept; + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include +#include + +#if _CCCL_HAS_HOST_STD_LIB() +# include +# include +#endif // _CCCL_HAS_HOST_STD_LIB() + +#include "literal.h" +#include "test_macros.h" + +template +TEST_FUNC constexpr void test() +{ + cuda::std::formatter formatter; + + static_assert(cuda::std::is_same_v{}))>); + static_assert(noexcept(formatter.set_separator(cuda::std::basic_string_view{}))); + + formatter.set_separator(cuda::std::basic_string_view{TEST_STRLIT(CharT, "sep")}); + + // Note there is no direct way to validate this function modified the object. +} + +template +TEST_FUNC constexpr void test() +{ + test>(); + test>(); + test>(); + test>(); + +#if _CCCL_HAS_HOST_STD_LIB() + NV_IF_TARGET(NV_IS_HOST, ({ + test>(); + test>(); + })) +#endif // _CCCL_HAS_HOST_STD_LIB() +} + +TEST_FUNC constexpr bool test() +{ + test(); +#if _CCCL_HAS_WCHAR_T() + test(); +#endif // _CCCL_HAS_WCHAR_T() + + return true; +} + +int main(int, char**) +{ + test(); + static_assert(test()); + + return 0; +} diff --git a/libcudacxx/test/support/format_functions_common.h b/libcudacxx/test/support/format_functions_common.h index 7befaaa605c..f027751bd4c 100644 --- a/libcudacxx/test/support/format_functions_common.h +++ b/libcudacxx/test/support/format_functions_common.h @@ -202,6 +202,9 @@ struct cuda::std::formatter { if (!parse_called) { +#if _CCCL_HAS_EXCEPTIONS() + NV_IF_TARGET(NV_IS_HOST, ({ throw parse_call_validator::parse_function_not_called{}; })) +#endif // _CCCL_HAS_EXCEPTIONS() assert(false); } return ctx.out(); diff --git a/libcudacxx/test/support/literal.h b/libcudacxx/test/support/literal.h index 5d7d63eade7..d06e43a0cb1 100644 --- a/libcudacxx/test/support/literal.h +++ b/libcudacxx/test/support/literal.h @@ -62,15 +62,15 @@ template # define TEST_CHARLIT(CharT, val) _test_charlit_impl(val, L##val, u##val, U##val) #endif // _CCCL_HAS_CHAR8_T() -template -[[nodiscard]] TEST_FUNC constexpr auto _test_strlit_impl( - [[maybe_unused]] const char (&char_str)[N], - [[maybe_unused]] const wchar_t (&wchar_str)[N], +template +[[nodiscard]] TEST_FUNC constexpr const auto& _test_strlit_impl( + [[maybe_unused]] const CharStr& char_str, + [[maybe_unused]] const WCharStr& wchar_str, #if _CCCL_HAS_CHAR8_T() - [[maybe_unused]] const char8_t (&char8_str)[N], + [[maybe_unused]] const Char8Str& char8_str, #endif // _CCCL_HAS_CHAR8_T() - [[maybe_unused]] const char16_t (&char16_str)[N], - [[maybe_unused]] const char32_t (&char32_str)[N]) noexcept -> const CharT (&)[N] + [[maybe_unused]] const Char16Str& char16_str, + [[maybe_unused]] const Char32Str& char32_str) noexcept { if constexpr (cuda::std::is_same_v) {