diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 74c5f383bfc..e6f1d9894d9 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -140,6 +140,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/experimental/unordered_set ${CMAKE_CURRENT_LIST_DIR}/inc/experimental/vector ${CMAKE_CURRENT_LIST_DIR}/inc/filesystem + ${CMAKE_CURRENT_LIST_DIR}/inc/format ${CMAKE_CURRENT_LIST_DIR}/inc/forward_list ${CMAKE_CURRENT_LIST_DIR}/inc/fstream ${CMAKE_CURRENT_LIST_DIR}/inc/functional diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index 3ba146c97bc..8c4d92c0ea5 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/format b/stl/inc/format new file mode 100644 index 00000000000..addbe13cdf7 --- /dev/null +++ b/stl/inc/format @@ -0,0 +1,357 @@ +// format standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _FORMAT_ +#define _FORMAT_ +#include +#if _STL_COMPILER_PREPROCESSOR +#ifndef __cpp_lib_concepts +#pragma message("The contents of are available only with C++20 concepts support.") +#else // ^^^ !defined(__cpp_lib_concepts) / defined(__cpp_lib_concepts) vvv + +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_STD_BEGIN + +class format_error : public runtime_error { + using runtime_error::runtime_error; +}; + +enum class _Align { _None, _Left, _Right, _Center }; + +struct _Auto_id_tag {}; + +// clang-format off +template +concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) { + { _At._On_align(_Aln) } -> same_as; + { _At._On_fill(_Sv) } -> same_as; + { _At._On_width(_STD declval()) } -> same_as; + { _At._On_dynamic_width(_STD declval()) } -> same_as; + { _At._On_dynamic_width(_STD declval<_Auto_id_tag>()) } -> same_as; +}; +template +concept _Parse_arg_id_callbacks = requires(_Ty _At) { + { _At._On_auto_id() } -> same_as; + { _At._On_manual_id(_STD declval()) } -> same_as; +}; +// clang-format on + +// we need to implement this ourselves because from_chars does not work with wide characters +template +constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, int& _Integer) { + _STL_INTERNAL_CHECK(_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); + unsigned int _Value = 0; + constexpr unsigned int _Max_int = static_cast((numeric_limits::max)()); + constexpr unsigned int _Big_int = _Max_int / 10; + + do { + if (_Value > _Big_int) { + _Value = _Max_int + 1; + break; + } + _Value = _Value * 10 + static_cast(*_Begin - '0'); + ++_Begin; + } while (_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); + if (_Value > _Max_int) { + throw format_error("Number is too big"); + } + _Integer = static_cast(_Value); + return _Begin; +} + +template _Callbacks_type> +constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + _STL_INTERNAL_CHECK(_Begin != _End); + _CharT _Ch = *_Begin; + // No id provided, format string is using automatic indexing. + if (_Ch == '}' || _Ch == ':') { + _Callbacks._On_auto_id(); + return _Begin; + } + if (_Ch >= '0' && _Ch <= '9') { + int _Index = 0; + // arg_id is not allowed to have any leading zeros, but is allowed to be + // equal to zero (but not '00'). So if _Ch is zero we skip the parsing, leave + // _Index set to zero and let the validity checks below ensure that the arg_id + // wasn't something like "00", or "023". + if (_Ch != '0') { + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Index); + } else { + ++_Begin; + } + // The format string shouldn't end right after the index number. + // The only things permitted after the index are the end of the replacement field ('}') + // or the beginning of the format spec (':'). + if (_Begin == _End || (*_Begin != '}' && *_Begin != ':')) { + throw format_error("Invalid format string."); + } + _Callbacks._On_manual_id(_Index); + return _Begin; + } + // This is where we would parse named arg ids if std::format were to support them. + throw format_error("Invalid format string."); +} + +template _Callbacks_type> +constexpr const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + // done with parsing, or reached the end of a replacement field. + if (_Begin == _End || *_Begin == '}') { + return _Begin; + } + + // align and fill + auto _Parsed_align = _Align::_None; + auto _Align_pt = _Begin + 1; + if (_Align_pt == _End) { + _Align_pt = _Begin; + } + for (;;) { + switch (*_Align_pt) { + case '<': + _Parsed_align = _Align::_Left; + break; + case '>': + _Parsed_align = _Align::_Right; + break; + case '^': + _Parsed_align = _Align::_Center; + break; + } + if (_Parsed_align != _Align::_None) { + if (_Align_pt != _Begin) { + if (*_Begin == '{') { + throw format_error("invalid fill character '{'"); + } + _Callbacks._On_fill({_Begin, static_cast(_Align_pt - _Begin)}); + _Begin = _Align_pt + 1; + } else { + ++_Begin; + } + _Callbacks._On_align(_Parsed_align); + break; + } else if (_Align_pt == _Begin) { + break; + } + _Align_pt = _Begin; + } + return _Begin; +} + +// Adapts a type modeling _Parse_spec_callbacks to model _Parse_arg_id_callbacks. +// Used in _Parse_width so that _Parse_arg_id can be used to parse dynamic widths. +template _Callbacks_type> +struct _Width_adapter { + _Callbacks_type& _Callbacks; + + explicit constexpr _Width_adapter(_Callbacks_type& _Handler) : _Callbacks(_Handler) {} + + constexpr void _On_auto_id() { + _Callbacks._On_dynamic_width(_Auto_id_tag{}); + } + constexpr void _On_manual_id(int _Id) { + _Callbacks._On_dynamic_width(_Id); + } +}; + +template _Callbacks_type> +constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + _STL_INTERNAL_CHECK(_Begin != _End); + if ('1' <= *_Begin && *_Begin <= '9') { + int _Value = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Value); + _Callbacks._On_width(_Value); + } else if (*_Begin == '{') { + ++_Begin; + if (_Begin != _End) { + _Begin = _Parse_arg_id(_Begin, _End, _Width_adapter<_CharT, _Callbacks_type>(_Callbacks)); + } + if (_Begin == _End || *_Begin != '}') { + throw format_error("Invalid format string."); + } + ++_Begin; + } + return _Begin; +} + +template +struct formatter; + +// TODO: test coverage +template +class basic_format_parse_context { +public: + using char_type = _CharT; + using const_iterator = typename basic_string_view<_CharT>::const_iterator; + using iterator = const_iterator; + +private: + basic_string_view<_CharT> _Format_string; + size_t _Num_args; + // The standard says this is size_t, however we use ptrdiff_t to save some space + // by not having to store the indexing mode. Below is a more detailed explanation + // of how this works. + ptrdiff_t _Next_arg_id = 0; + +public: + constexpr explicit basic_format_parse_context(basic_string_view<_CharT> _Fmt, size_t _Num_args_ = 0) noexcept + : _Format_string(_Fmt), _Num_args(_Num_args_) {} + basic_format_parse_context(const basic_format_parse_context&) = delete; + basic_format_parse_context& operator=(const basic_format_parse_context&) = delete; + + _NODISCARD constexpr const_iterator begin() const noexcept { + return _Format_string.begin(); + } + _NODISCARD constexpr const_iterator end() const noexcept { + return _Format_string.end(); + } + constexpr void advance_to(const_iterator _It) { + _Format_string.remove_prefix(_It - begin()); + } + + // While the standard presents an exposition only enum value for + // the indexing mode (manual, automatic, or unknown) we use _Next_arg_id to indicate it. + // _Next_arg_id == 0 means unknown + // _Next_arg_id > 0 means automatic + // _Next_arg_id == -1 means manual + constexpr size_t next_arg_id() { + if (_Next_arg_id >= 0) { + return _Next_arg_id++; + } + throw format_error("Can not switch from manual to automatic indexing"); + } + constexpr void check_arg_id(size_t _Id) { + (void) _Id; + if (_Next_arg_id > 0) { + throw format_error("Can not switch from automatic to manual indexing"); + } + _Next_arg_id = -1; + } +}; + +// TODO: test coverage +template +class basic_format_arg { +public: + class handle; + +private: + using _Char_type = typename _Context::char_type; + + using _Format_arg_value = variant, handle>; + + _Format_arg_value _Value; + +public: + class handle { + private: + const void* _Ptr; + void (*_Format)(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context _Format_ctx, const void*); + friend basic_format_arg; + + public: + void format(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context& _Format_ctx) { + _Format(_Parse_ctx, _Format_ctx, _Ptr); + } + }; + + basic_format_arg() noexcept = default; + explicit operator bool() const noexcept { + return !_STD holds_alternative(_Value); + } +}; + +// TODO: test coverage +template +class _Format_arg_store { + static constexpr size_t _Num_args = sizeof...(_Args); + // TODO: no arg packing yet + + using _Value_type = basic_format_arg<_Context>; +}; + +// TODO: test coverage +template +class basic_format_args { +public: + basic_format_args() noexcept; + template + basic_format_args(const _Format_arg_store<_Context, _Args...>& store) noexcept; + + basic_format_arg<_Context> get(size_t _Index) const noexcept; +}; + +// TODO: test coverage +// clang-format off +template + requires output_iterator<_Out, _CharT> +class basic_format_context { + // clang-format on +private: + _Out _OutputIt; + basic_format_args _Args; + locale _Loc; + +public: + using iterator = _Out; + using char_type = _CharT; + + template + using formatter_type = formatter<_Ty, _CharT>; + + basic_format_arg arg(size_t _Id) const { + return _Args.get(_Id); + } + locale locale() { + return _Loc; + } + + iterator out() { + return _OutputIt; + } + void advance_to(iterator _It) { + // TODO: IDL support probably required + _OutputIt = _It; + } +}; + +using format_context = basic_format_context, string::value_type>; +using wformat_context = basic_format_context, wstring::value_type>; +using format_args = basic_format_args; +using wformat_args = basic_format_args; + +// FUNCTION vformat +string vformat(string_view _Fmt, format_args _Args); +wstring vformat(wstring_view _Fmt, wformat_args _Args); +string vformat(const locale& _Loc, string_view _Fmt, format_args _Args); +wstring vformat(const locale& _Loc, wstring_view _Fmt, wformat_args _Args); + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) + +#endif // __cpp_lib_concepts +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _FORMAT_ diff --git a/tests/std/test.lst b/tests/std/test.lst index da7a77478ed..826251c4c55 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -235,6 +235,8 @@ tests\P0595R2_is_constant_evaluated tests\P0607R0_inline_variables tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants +tests\P0645R10_text_formatting_parse_contexts +tests\P0645R10_text_formatting_parsing tests\P0674R1_make_shared_for_arrays tests\P0718R2_atomic_smart_ptrs tests\P0758R1_is_nothrow_convertible diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp new file mode 100644 index 00000000000..7a3f5c0dad2 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +// TODO: fill in tests + +int main() { + return 0; +} diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/env.lst b/tests/std/tests/P0645R10_text_formatting_parsing/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parsing/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp new file mode 100644 index 00000000000..89aec01765a --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +using namespace std; + +// copied from the string_view tests +template +struct choose_literal; // not defined + +template <> +struct choose_literal { + static constexpr const char* choose(const char* s, const wchar_t*) { + return s; + } +}; + +template <> +struct choose_literal { + static constexpr const wchar_t* choose(const char*, const wchar_t* s) { + return s; + } +}; + +#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) + +template +struct testing_callbacks { + _Align expected_alignment = _Align::_None; + basic_string_view expected_fill; + int expected_width = -1; + int expected_dynamic_width = -1; + bool expected_auto_dynamic_width = false; + constexpr void _On_align(_Align aln) { + assert(aln == expected_alignment); + } + constexpr void _On_fill(basic_string_view str_view) { + assert(str_view == expected_fill); + } + constexpr void _On_width(int width) { + assert(width == expected_width); + } + constexpr void _On_dynamic_width(int id) { + assert(id == expected_dynamic_width); + } + constexpr void _On_dynamic_width(_Auto_id_tag) { + assert(expected_auto_dynamic_width); + } +}; +template +testing_callbacks(_Align, basic_string_view) -> testing_callbacks; + +struct testing_arg_id_callbacks { + constexpr void _On_auto_id() {} + constexpr void _On_manual_id(int) {} +}; + +template +constexpr void test_parse_helper(const CharT* (*func)(const CharT*, const CharT*, callback_type&&), + basic_string_view view, bool err_expected = false, + typename basic_string_view::size_type expected_end_position = basic_string_view::npos, + callback_type&& callbacks = {}) { + try { + auto end = func(view.data(), view.data() + view.size(), move(callbacks)); + if (expected_end_position != basic_string_view::npos) { + assert(end == view.data() + expected_end_position); + } + assert(!err_expected); + } catch (const format_error&) { + assert(err_expected); + } +} + +template +constexpr bool test_parse_align() { + auto parse_align_fn = _Parse_align>; + using view_typ = basic_string_view; + + auto s0 = view_typ(TYPED_LITERAL(CharT, "")); + auto s1 = view_typ(TYPED_LITERAL(CharT, "*<")); + auto s2 = view_typ(TYPED_LITERAL(CharT, "*>")); + auto s3 = view_typ(TYPED_LITERAL(CharT, "*^")); + + test_parse_helper(parse_align_fn, s0, false, view_typ::npos, {_Align::_None, view_typ(TYPED_LITERAL(CharT, ""))}); + test_parse_helper(parse_align_fn, s1, false, view_typ::npos, {_Align::_Left, view_typ(TYPED_LITERAL(CharT, "*"))}); + test_parse_helper(parse_align_fn, s2, false, view_typ::npos, {_Align::_Right, view_typ(TYPED_LITERAL(CharT, "*"))}); + test_parse_helper( + parse_align_fn, s3, false, view_typ::npos, {_Align::_Center, view_typ(TYPED_LITERAL(CharT, "*"))}); + if constexpr (same_as) { + // This is a CJK character where the least significant byte is the same as ascii '>', + // libfmt and initial drafts of narrowed characters when parsing alignments, causing + // \x343E (which is from CJK unified ideographs extension A) and similar characters to parse as + // an alignment specifier. + auto s4 = L"*\x343E"sv; + test_parse_helper(parse_align_fn, s4, false, view_typ::npos, {_Align::_None, L"*"sv}); + } + + return true; +} + +template +constexpr bool test_parse_width() { + auto parse_width_fn = _Parse_width>; + using view_typ = basic_string_view; + + auto s0 = view_typ(TYPED_LITERAL(CharT, "1")); + auto s1 = view_typ(TYPED_LITERAL(CharT, "{1}")); + auto s2 = view_typ(TYPED_LITERAL(CharT, "{0}")); + auto s3 = view_typ(TYPED_LITERAL(CharT, "{}")); + auto i0 = view_typ(TYPED_LITERAL(CharT, "0")); + auto i1 = view_typ(TYPED_LITERAL(CharT, "01")); + + test_parse_helper(parse_width_fn, s0, false, view_typ::npos, {.expected_width = 1}); + test_parse_helper(parse_width_fn, s1, false, view_typ::npos, {.expected_dynamic_width = 1}); + test_parse_helper(parse_width_fn, s2, false, view_typ::npos, {.expected_dynamic_width = 0}); + test_parse_helper(parse_width_fn, s3, false, view_typ::npos, {.expected_auto_dynamic_width = true}); + test_parse_helper(parse_width_fn, i0, false, false); + test_parse_helper(parse_width_fn, i1, false, false); + return true; +} + +template +constexpr bool test_parse_arg_id() { + auto parse_arg_id_fn = _Parse_arg_id; + using view_typ = basic_string_view; + // note that parse arg id starts with the arg id itself, not the { beginning of the + // format spec + auto s0 = view_typ(TYPED_LITERAL(CharT, "}")); + auto s1 = view_typ(TYPED_LITERAL(CharT, ":")); + auto s2 = view_typ(TYPED_LITERAL(CharT, ":}")); + auto s3 = view_typ(TYPED_LITERAL(CharT, "0:}")); + auto s4 = view_typ(TYPED_LITERAL(CharT, "0:")); + auto s5 = view_typ(TYPED_LITERAL(CharT, "1}")); + auto i0 = view_typ(TYPED_LITERAL(CharT, "01}")); + auto i1 = view_typ(TYPED_LITERAL(CharT, "0")); + + test_parse_helper(parse_arg_id_fn, s0, false, 0); + test_parse_helper(parse_arg_id_fn, s1, false, 0); + test_parse_helper(parse_arg_id_fn, s2, false, 0); + test_parse_helper(parse_arg_id_fn, s3, false, 1); + test_parse_helper(parse_arg_id_fn, s4, false, 1); + test_parse_helper(parse_arg_id_fn, s5, false, 1); + + // can't test the expected exceptions in a constexpr + // context + if (!is_constant_evaluated()) { + test_parse_helper(parse_arg_id_fn, i0, true); + test_parse_helper(parse_arg_id_fn, i1, true); + } + + return true; +} + +int main() { + test_parse_align(); + test_parse_align(); + static_assert(test_parse_align()); + static_assert(test_parse_align()); + test_parse_arg_id(); + test_parse_arg_id(); + static_assert(test_parse_arg_id()); + static_assert(test_parse_arg_id()); + test_parse_width(); + test_parse_width(); + static_assert(test_parse_width()); + static_assert(test_parse_width()); + return 0; +} diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 71cb23f8d3d..6ca12287dac 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -24,6 +24,7 @@ PM_CL="/DMEOW_HEADER=deque" PM_CL="/DMEOW_HEADER=exception" PM_CL="/DMEOW_HEADER=execution" PM_CL="/DMEOW_HEADER=filesystem" +PM_CL="/DMEOW_HEADER=format" PM_CL="/DMEOW_HEADER=forward_list" PM_CL="/DMEOW_HEADER=fstream" PM_CL="/DMEOW_HEADER=functional"