diff --git a/stl/inc/format b/stl/inc/format index 83631ab8089..65b9145847a 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -12,6 +12,7 @@ #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 @@ -20,6 +21,7 @@ #include #include #include +#include #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) @@ -38,6 +40,30 @@ enum class _Align { _None, _Left, _Right, _Center }; enum class _Sign { _None, _Plus, _Minus, _Space }; +enum class _Basic_format_arg_type : uint8_t { + _None, + _Int_type, + _UInt_type, + _Long_long_type, + _ULong_long_type, + _Bool_type, + _Char_type, + _Float_type, + _Double_type, + _Long_double_type, + _Pointer_type, + _CString_type, + _String_type, + _Custom_type, +}; +static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); + +constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) { + return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_ULong_long_type; +} +constexpr bool _Is_arithmetic_fmt_type(_Basic_format_arg_type _Ty) { + return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Long_double_type; +} struct _Auto_id_tag {}; // clang-format off @@ -45,29 +71,218 @@ template concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln, _Sign _Sgn) { { _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; - { _At._On_precision(_STD declval()) } -> same_as; - { _At._On_dynamic_precision(_STD declval()) } -> same_as; - { _At._On_dynamic_precision(_STD declval<_Auto_id_tag>()) } -> same_as; + { _At._On_width(static_cast(0)) } -> same_as; + { _At._On_dynamic_width(size_t{}) } -> same_as; + { _At._On_dynamic_width(_Auto_id_tag{}) } -> same_as; + { _At._On_precision(static_cast(0)) } -> same_as; + { _At._On_dynamic_precision(size_t{}) } -> same_as; + { _At._On_dynamic_precision(_Auto_id_tag{}) } -> same_as; { _At._On_sign(_Sgn) } -> same_as; { _At._On_hash() } -> same_as; { _At._On_zero() } -> same_as; - { _At._On_type(_STD declval<_CharT>()) } -> same_as; + { _At._On_type(_CharT{}) } -> 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; + { _At._On_manual_id(size_t{}) } -> same_as; }; + +template +concept _Parse_replacement_field_callbacks = requires(_Ty _At, const _CharT* _Begin, const _CharT* _End) { + { _At._Parse_context }; + { _At._On_text(_Begin, _End) } -> same_as; + { _At._On_replacement_field(size_t{}, static_cast(nullptr)) } -> same_as; + { _At._On_format_specs(size_t{}, _Begin, _End) } -> same_as; +}; + // clang-format on +template +struct formatter; + +inline void _You_see_this_error_because_arg_id_is_out_of_range() noexcept {} + +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; + + 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 const_iterator _It) { + _Adl_verify_range(_It, _Format_string.end()); + // _It must be after _Format_string.begin(). + const auto _Diff = static_cast(_It._Unwrapped() - _Format_string._Unchecked_begin()); + _Format_string.remove_prefix(_Diff); + } + + // 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 automatic + // _Next_arg_id == 0 means unknown + // _Next_arg_id < 0 means manual + constexpr size_t next_arg_id() { + if (_Next_arg_id < 0) { + throw format_error("Can not switch from manual to automatic indexing"); + } + + return static_cast(_Next_arg_id++); + } + + constexpr void check_arg_id(const size_t _Id) { + if (_STD is_constant_evaluated()) { + if (_Id >= _Num_args) { + _You_see_this_error_because_arg_id_is_out_of_range(); + } + } + + if (_Next_arg_id > 0) { + throw format_error("Can not switch from automatic to manual indexing"); + } + _Next_arg_id = -1; + } + +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. Above is a more detailed explanation + // of how this works. + ptrdiff_t _Next_arg_id = 0; +}; + +template +class basic_format_arg { +public: + using _CharType = typename _Context::char_type; + + class handle { + private: + const void* _Ptr; + void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context _Format_ctx, const void*); + friend basic_format_arg; + + public: + template + explicit handle(const _Ty& _Val) noexcept + : _Ptr(_STD addressof(_Val)), + _Format([](basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void* _Ptr) { + typename _Context::template formatter_type<_Ty> _Formatter; + _Parse_ctx.advance_to(_Formatter.parse(_Parse_ctx)); + _Format_ctx.advance_to(_Formatter.format(*static_cast(_Ptr), _Format_ctx)); + }) {} + + void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) { + _Format(_Parse_ctx, _Format_ctx, _Ptr); + } + }; + + // TRANSITION, LLVM-49072 + basic_format_arg() noexcept : _Active_state(_Basic_format_arg_type::_None), _No_state() {} + + explicit basic_format_arg(const int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Int_type), _Int_state(_Val) {} + explicit basic_format_arg(const unsigned int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_UInt_type), _UInt_state(_Val) {} + explicit basic_format_arg(const long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_long_type), _Long_long_state(_Val) {} + explicit basic_format_arg(const unsigned long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_ULong_long_type), _ULong_long_state(_Val) {} + explicit basic_format_arg(const bool _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Bool_type), _Bool_state(_Val) {} + explicit basic_format_arg(const _CharType _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Char_type), _Char_state(_Val) {} + explicit basic_format_arg(const float _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Float_type), _Float_state(_Val) {} + explicit basic_format_arg(const double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Double_type), _Double_state(_Val) {} + explicit basic_format_arg(const long double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_double_type), _Long_double_state(_Val) {} + explicit basic_format_arg(const void* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Pointer_type), _Pointer_state(_Val) {} + explicit basic_format_arg(const _CharType* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_CString_type), _CString_state(_Val) {} + explicit basic_format_arg(const basic_string_view<_CharType> _Val) noexcept + : _Active_state(_Basic_format_arg_type::_String_type), _String_state(_Val) {} + explicit basic_format_arg(const handle _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Custom_type), _Custom_state(_Val) {} + explicit operator bool() const noexcept { + return _Active_state != _Basic_format_arg_type::_None; + } + + _Basic_format_arg_type _Active_state = _Basic_format_arg_type::_None; + union { + monostate _No_state = monostate{}; + int _Int_state; + unsigned int _UInt_state; + long long _Long_long_state; + unsigned long long _ULong_long_state; + bool _Bool_state; + _CharType _Char_state; + float _Float_state; + double _Double_state; + long double _Long_double_state; + const void* _Pointer_state; + const _CharType* _CString_state; + basic_string_view<_CharType> _String_state; + handle _Custom_state; + }; +}; + +template +auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { + switch (_Arg._Active_state) { + case _Basic_format_arg_type::_None: + return _Vis(_Arg._No_state); + case _Basic_format_arg_type::_Int_type: + return _Vis(_Arg._Int_state); + case _Basic_format_arg_type::_UInt_type: + return _Vis(_Arg._UInt_state); + case _Basic_format_arg_type::_Long_long_type: + return _Vis(_Arg._Long_long_state); + case _Basic_format_arg_type::_ULong_long_type: + return _Vis(_Arg._ULong_long_state); + case _Basic_format_arg_type::_Bool_type: + return _Vis(_Arg._Bool_state); + case _Basic_format_arg_type::_Char_type: + return _Vis(_Arg._Char_state); + case _Basic_format_arg_type::_Float_type: + return _Vis(_Arg._Float_state); + case _Basic_format_arg_type::_Double_type: + return _Vis(_Arg._Double_state); + case _Basic_format_arg_type::_Long_double_type: + return _Vis(_Arg._Long_double_state); + case _Basic_format_arg_type::_Pointer_type: + return _Vis(_Arg._Pointer_state); + case _Basic_format_arg_type::_CString_type: + return _Vis(_Arg._CString_state); + case _Basic_format_arg_type::_String_type: + return _Vis(_Arg._String_state); + case _Basic_format_arg_type::_Custom_type: + return _Vis(_Arg._Custom_state); + default: + _STL_VERIFY(false, "basic_format_arg is in impossible state"); + return _Vis(0); + } +} + // 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) { +constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, unsigned int& _Value) { _STL_INTERNAL_CHECK(_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); - unsigned int _Value = 0; + _Value = 0; constexpr unsigned int _Max_int = static_cast((numeric_limits::max)()); constexpr unsigned int _Big_int = _Max_int / 10; @@ -82,7 +297,6 @@ constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _ if (_Value > _Max_int) { throw format_error("Number is too big"); } - _Integer = static_cast(_Value); return _Begin; } @@ -97,7 +311,7 @@ constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, } if (_Ch >= '0' && _Ch <= '9') { - int _Index = 0; + unsigned 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 @@ -125,7 +339,9 @@ constexpr const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _ _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); // align and fill auto _Parsed_align = _Align::_None; - auto _Align_pt = _Begin + 1; + + // TODO: should increment one code point + auto _Align_pt = _Begin + 1; if (_Align_pt == _End) { _Align_pt = _Begin; } @@ -172,7 +388,7 @@ struct _Width_adapter { constexpr void _On_auto_id() { _Callbacks._On_dynamic_width(_Auto_id_tag{}); } - constexpr void _On_manual_id(int _Id) { + constexpr void _On_manual_id(size_t _Id) { _Callbacks._On_dynamic_width(_Id); } }; @@ -188,17 +404,36 @@ struct _Precision_adapter { constexpr void _On_auto_id() { _Callbacks._On_dynamic_precision(_Auto_id_tag{}); } - constexpr void _On_manual_id(int _Id) { + constexpr void _On_manual_id(size_t _Id) { _Callbacks._On_dynamic_precision(_Id); } }; +// _Parse_arg_id expects a handler when it finds an argument id, however +// _Parse_replacement_field actually needs to know the value of that argument ID to pass on +// to _Handler._On_replacement_field or _Handler._On_format_specs. This _Parse_arg_id wrapper +// stores the value of the arg id for later use, so _Parse_replacement_field has access to it. +template +struct _Id_adapter { + basic_format_parse_context<_CharT>& _Parse_context; + size_t _Arg_id = static_cast(-1); + constexpr void _On_auto_id() { + _Arg_id = _Parse_context.next_arg_id(); + _STL_INTERNAL_CHECK(_Arg_id != static_cast(-1)); + } + constexpr void _On_manual_id(size_t _Id) { + _Parse_context.check_arg_id(_Id); + _Arg_id = _Id; + _STL_INTERNAL_CHECK(_Arg_id != static_cast(-1)); + } +}; + 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); + unsigned int _Value = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Value); _Callbacks._On_width(_Value); } else if (*_Begin == '{') { ++_Begin; @@ -222,8 +457,8 @@ constexpr const _CharT* _Parse_precision(const _CharT* _Begin, const _CharT* _En } if ('0' <= _Ch && _Ch <= '9') { - int _Precision = 0; - _Begin = _Parse_nonnegative_integer(_Begin, _End, _Precision); + unsigned int _Precision = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Precision); _Callbacks._On_precision(_Precision); } else if (_Ch == '{') { ++_Begin; @@ -302,202 +537,307 @@ constexpr const _CharT* _Parse_format_specs(const _CharT* _Begin, const _CharT* return _Begin; } -template -struct formatter; +template _HandlerT> +constexpr const _CharT* _Parse_replacement_field(const _CharT* _Begin, const _CharT* _End, _HandlerT&& _Handler) { + ++_Begin; + if (_Begin == _End) { + throw format_error("Invalid format string."); + } + + if (*_Begin == '}') { + // string was "{}", and we have a replacement field + _Handler._On_replacement_field(_Handler._Parse_context.next_arg_id(), _Begin); + } else if (*_Begin == '{') { + // string was "{{", so we have a literal "{" to print + _Handler._On_text(_Begin, _Begin + 1); + } else { + _Id_adapter<_CharT> _Adapter{_Handler._Parse_context}; + _Begin = _Parse_arg_id(_Begin, _End, _Adapter); + _CharT _Ch = _CharT{}; + if (_Begin != _End) { + _Ch = *_Begin; + } + if (_Ch == '}') { + _Handler._On_replacement_field(_Adapter._Arg_id, _Begin); + } else if (_Ch == ':') { + _Begin = _Handler._On_format_specs(_Adapter._Arg_id, _Begin + 1, _End); + if (_Begin == _End || *_Begin != '}') { + throw format_error("Unknown format specifier."); + } + } else { + throw format_error("Missing '}' in format string."); + } + } + return _Begin + 1; +} + +template _HandlerT> +constexpr void _Parse_format_string(basic_string_view<_CharT> _Format_str, _HandlerT&& _Handler) { + auto _Begin = _Format_str.data(); + auto _End = _Begin + _Format_str.size(); + while (_Begin != _End) { + const _CharT* _OpeningCurl = _Begin; + if (*_Begin != '{') { + // we didn't start at an opening curl, find the next one + _OpeningCurl = _STD find(_Begin + 1, _End, '{'); + for (;;) { + const _CharT* _ClosingCurl = _Find_unchecked(_Begin, _OpeningCurl, '}'); + + // In this case we didn't find either a closing curl or opening curl. + // Write the whole thing out. + if (_ClosingCurl == _OpeningCurl) { + return _Handler._On_text(_Begin, _OpeningCurl); + } + // We know _ClosingCurl isn't past the end because + // the above condition was not met. + ++_ClosingCurl; + if (_ClosingCurl == _OpeningCurl || *_ClosingCurl != '}') { + throw format_error("Unmatched '}' in format string."); + } + // We found two closing curls, so output only one of them + _Handler._On_text(_Begin, _ClosingCurl); + + // skip over the second closing curl + _Begin = _ClosingCurl + 1; + } + + // We are done, there were no replacement fields. + if (_OpeningCurl == _End) { + return; + } + } + // Parse the replacement field starting at _OpeningCurl and ending sometime before _End. + _Begin = _Parse_replacement_field(_OpeningCurl, _End, _Handler); + } +} -inline void _You_see_this_error_because_arg_id_is_out_of_range() noexcept {} template -class basic_format_parse_context { +struct _Basic_format_specs { + unsigned int _Width; + unsigned int _Precision; + char _Type; + _Align _Alignment; + _Sign _Sgn; + bool _Alt; + // At most one codepoint (so one char32_t or four utf-8 char8_t). + _CharT _Fill[4]; +}; + +// Model of _Parse_specs_callbacks that fills a _Basic_format_specs with the parsed data. +template +class _Specs_setter { public: - using char_type = _CharT; - using const_iterator = typename basic_string_view<_CharT>::const_iterator; - using iterator = const_iterator; + explicit constexpr _Specs_setter(_Basic_format_specs<_CharT> _Specs_) : _Specs(_Specs_) {} - 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; + constexpr void _On_align(_Align _Aln) { + _Specs._Alignment = _Aln; + } - _NODISCARD constexpr const_iterator begin() const noexcept { - return _Format_string.begin(); + constexpr void _On_fill(basic_string_view<_CharT> _Sv) { + if (_Sv.size() > 4) { + throw format_error("Invalid fill (too long)."); + } + _STD fill(_Specs._Fill, _Specs._Fill + 4, _CharT{}); + _STD copy(_Sv.begin(), _Sv.end(), _Specs._Fill); } - _NODISCARD constexpr const_iterator end() const noexcept { - return _Format_string.end(); + + constexpr void _On_sign(_Sign _Sgn) { + _Specs._Sgn = _Sgn; } - constexpr void advance_to(const const_iterator _It) { - _Adl_verify_range(_It, _Format_string.end()); - const auto _Diff = static_cast(_It._Unwrapped() - _Format_string._Unchecked_begin()); - _Format_string.remove_prefix(_Diff); + + constexpr void _On_hash() { + _Specs._Alt = true; } - // 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 automatic - // _Next_arg_id == 0 means unknown - // _Next_arg_id < 0 means manual - constexpr size_t next_arg_id() { - if (_Next_arg_id < 0) { - throw format_error("Can not switch from manual to automatic indexing"); - } + constexpr void _On_zero() { + _Specs._Alignment = _Align::_None; + _Specs._Fill[0] = _CharT{'0'}; + } - return static_cast(_Next_arg_id++); + constexpr void _On_width(unsigned int _Width) { + _Specs._Width = _Width; } - constexpr void check_arg_id(const size_t _Id) { - if (_STD is_constant_evaluated()) { - if (_Id >= _Num_args) { - _You_see_this_error_because_arg_id_is_out_of_range(); + constexpr void _On_precision(unsigned int _Precision) { + _Specs._Precision = _Precision; + } + + constexpr void _On_type(_CharT _Type) { + _Specs._Type = static_cast<_CharT>(_Type); + } + +protected: + _Basic_format_specs<_CharT> _Specs; +}; + +template +constexpr basic_format_arg<_Context> _Get_arg(_Context _Ctx, size_t _Arg_id) { + // note: while this is parameterized on the _Arg_id type in libfmt we don't + // need to do that in std::format because it's only called with either an integer + // id or a named id (which we do not support in std::format) + auto _Arg = _Ctx.arg(_Arg_id); + if (!_Arg) { + throw format_error("Argument not found."); + } + return _Arg; +} + +// Checks that the type and value of an argument associated with a dynamic +// width specifier are valid. +class _Width_checker { +public: + template + constexpr unsigned long long operator()(_Ty _Value) { + if constexpr (is_integral_v<_Ty>) { + if constexpr (is_signed_v<_Ty>) { + if (_Value < 0) { + throw format_error("Negative width."); + } } + return static_cast(_Value); + } else { + throw format_error("Width is not an integer."); } + } +}; - if (_Next_arg_id > 0) { - throw format_error("Can not switch from automatic to manual indexing"); +// Checks that the type and value of an argument associated with a dynamic +// precision specifier are valid. +class _Precision_checker { +public: + template + constexpr unsigned long long operator()(_Ty _Value) { + if constexpr (is_integral_v<_Ty>) { + if constexpr (is_signed_v<_Ty>) { + if (_Value < 0) { + throw format_error("Negative precision."); + } + } + return static_cast(_Value); + } else { + throw format_error("Precision is not an integer."); } - _Next_arg_id = -1; + } +}; + +// Parses standard format specs into a _Basic_format_specs using _Specs_setter, and, +// in addition handles dynamic width and precision. This is separate from _Specs setter +// because it needs to know about the current basic_format_parse_context and basic_format_context +// in order to fetch the width from the arguments. +template +class _Specs_handler : public _Specs_setter { +public: + using _CharT = typename _Context::char_type; + + constexpr _Specs_handler(_Basic_format_specs<_CharT>& _Specs_, _ParseContext& _Parse_ctx_, _Context& _Ctx_) + : _Specs_setter<_CharT>(_Specs_), _Parse_ctx(_Parse_ctx_), _Ctx(_Ctx_) {} + + template + constexpr void _On_dynamic_width(_Id _Arg_id) { + this->_Specs._Width = _Get_dynamic_specs<_Width_checker>(_Get_arg(_Arg_id)); + } + + template + constexpr void _On_dynamic_precision(_Id _Arg_id) { + this->_Specs._Precision = _Get_dynamic_specs<_Precision_checker>(_Get_arg(_Arg_id)); } 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. Above is a more detailed explanation - // of how this works. - ptrdiff_t _Next_arg_id = 0; -}; + _ParseContext& _Parse_ctx; + _Context& _Ctx; -enum class _Basic_format_arg_type : uint8_t { - _None, - _Int_type, - _UInt_type, - _Long_long_type, - _ULong_long_type, - _Bool_type, - _Char_type, - _Float_type, - _Double_type, - _Long_double_type, - _Pointer_type, - _CString_type, - _String_type, - _Custom_type, + constexpr basic_format_arg<_Context> _Get_arg(_Auto_id_tag) { + return _STD _Get_arg(_Ctx, _Parse_ctx.next_arg_id()); + } + + constexpr basic_format_arg<_Context> _Get_arg(size_t _Arg_id) { + _Parse_ctx.check_arg_id(_Arg_id); + return _STD _Get_arg(_Ctx, _Arg_id); + } + + // Fetch the value of an argument associated with a dynamic + // width or precision specifier. This will be called with either + // _Width_checker or _Precision_checker as "_Handler". + template + static constexpr unsigned int _Get_dynamic_specs(_FormatArg _Arg) { + unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); + if (_Val > (numeric_limits::max)()) { + throw format_error("Number is too big."); + } + return static_cast(_Val); + } }; -static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); -template -class basic_format_arg { +class _Numeric_specs_checker { +private: + _Basic_format_arg_type _Arg_type = _Basic_format_arg_type::_None; + public: - using _CharType = typename _Context::char_type; + constexpr explicit _Numeric_specs_checker(_Basic_format_arg_type _Arg_type_) : _Arg_type(_Arg_type_) {} - class handle { - private: - const void* _Ptr; - void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context _Format_ctx, const void*); - friend basic_format_arg; + constexpr void _Require_numeric_argument() const { + if (!_Is_arithmetic_fmt_type(_Arg_type)) { + throw format_error("Format specifier requires numeric argument."); + } + } - public: - template - explicit handle(const _Ty& _Val) noexcept - : _Ptr(_STD addressof(_Val)), - _Format([](basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void* _Ptr) { - typename _Context::template formatter_type<_Ty> _Formatter; - _Parse_ctx.advance_to(_Formatter.parse(_Parse_ctx)); - _Format_ctx.advance_to(_Formatter.format(*static_cast(_Ptr), _Format_ctx)); - }) {} + constexpr void _Check_sign() const { + _Require_numeric_argument(); + if (_Is_integral_fmt_type(_Arg_type) && _Arg_type != _Basic_format_arg_type::_Int_type + && _Arg_type != _Basic_format_arg_type::_Long_long_type + && _Arg_type != _Basic_format_arg_type::_Char_type) { + throw format_error("Format specifier requires signed argument."); + } + } - void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) { - _Format(_Parse_ctx, _Format_ctx, _Ptr); + constexpr void _Check_precision() const { + if (_Is_integral_fmt_type(_Arg_type) || _Arg_type == _Basic_format_arg_type::_Pointer_type) { + throw format_error("Precision not allowed for this argument type."); } - }; + } +}; - // TRANSITION, LLVM-49072 - basic_format_arg() noexcept : _Active_state(_Basic_format_arg_type::_None), _No_state() {} +// Uses _Numeric_specs_checker to check that the type of the argument printed by +// a replacement field with format specs actually satisfies the requirements for +// that format spec. If the requirements are met then calls the base class +// handler method. +template +class _Specs_checker : public _Handler { +private: + _Numeric_specs_checker _Numeric_checker; - explicit basic_format_arg(const int _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Int_type), _Int_state(_Val) {} - explicit basic_format_arg(const unsigned int _Val) noexcept - : _Active_state(_Basic_format_arg_type::_UInt_type), _UInt_state(_Val) {} - explicit basic_format_arg(const long long _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Long_long_type), _Long_long_state(_Val) {} - explicit basic_format_arg(const unsigned long long _Val) noexcept - : _Active_state(_Basic_format_arg_type::_ULong_long_type), _ULong_long_state(_Val) {} - explicit basic_format_arg(const bool _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Bool_type), _Bool_state(_Val) {} - explicit basic_format_arg(const _CharType _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Char_type), _Char_state(_Val) {} - explicit basic_format_arg(const float _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Float_type), _Float_state(_Val) {} - explicit basic_format_arg(const double _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Double_type), _Double_state(_Val) {} - explicit basic_format_arg(const long double _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Long_double_type), _Long_double_state(_Val) {} - explicit basic_format_arg(const void* _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Pointer_type), _Pointer_state(_Val) {} - explicit basic_format_arg(const _CharType* _Val) noexcept - : _Active_state(_Basic_format_arg_type::_CString_type), _CString_state(_Val) {} - explicit basic_format_arg(const basic_string_view<_CharType> _Val) noexcept - : _Active_state(_Basic_format_arg_type::_String_type), _String_state(_Val) {} - explicit basic_format_arg(const handle _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Custom_type), _Custom_state(_Val) {} - explicit operator bool() const noexcept { - return _Active_state != _Basic_format_arg_type::_None; +public: + constexpr explicit _Specs_checker(const _Handler& _Handler_inst, _Basic_format_arg_type _Arg_type_) + : _Handler(_Handler_inst), _Numeric_checker(_Arg_type_) {} + + // _On_align has no checking, since we don't implement numeric alignments. + + constexpr void _On_sign(_Sign _Sgn) { + _Numeric_checker._Check_sign(); + _Handler::_On_sign(_Sgn); } - _Basic_format_arg_type _Active_state = _Basic_format_arg_type::_None; - union { - monostate _No_state = monostate{}; - int _Int_state; - unsigned int _UInt_state; - long long _Long_long_state; - unsigned long long _ULong_long_state; - bool _Bool_state; - _CharType _Char_state; - float _Float_state; - double _Double_state; - long double _Long_double_state; - const void* _Pointer_state; - const _CharType* _CString_state; - basic_string_view<_CharType> _String_state; - handle _Custom_state; - }; -}; + constexpr void _On_hash() { + // Note that '#' is not valid for CharT or bool unless you + // pass a numeric presentation type, but we encounter '#' before + // the presentation type so we can not check that requirement here + _Numeric_checker._Require_numeric_argument(); + _Handler::_On_hash(); + } -template -auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { - switch (_Arg._Active_state) { - case _Basic_format_arg_type::_None: - return _Vis(_Arg._No_state); - case _Basic_format_arg_type::_Int_type: - return _Vis(_Arg._Int_state); - case _Basic_format_arg_type::_UInt_type: - return _Vis(_Arg._UInt_state); - case _Basic_format_arg_type::_Long_long_type: - return _Vis(_Arg._Long_long_state); - case _Basic_format_arg_type::_ULong_long_type: - return _Vis(_Arg._ULong_long_state); - case _Basic_format_arg_type::_Bool_type: - return _Vis(_Arg._Bool_state); - case _Basic_format_arg_type::_Char_type: - return _Vis(_Arg._Char_state); - case _Basic_format_arg_type::_Float_type: - return _Vis(_Arg._Float_state); - case _Basic_format_arg_type::_Double_type: - return _Vis(_Arg._Double_state); - case _Basic_format_arg_type::_Long_double_type: - return _Vis(_Arg._Long_double_state); - case _Basic_format_arg_type::_Pointer_type: - return _Vis(_Arg._Pointer_state); - case _Basic_format_arg_type::_CString_type: - return _Vis(_Arg._CString_state); - case _Basic_format_arg_type::_String_type: - return _Vis(_Arg._String_state); - case _Basic_format_arg_type::_Custom_type: - return _Vis(_Arg._Custom_state); - default: - _STL_VERIFY(false, "basic_format_arg is in impossible state"); - return _Vis(0); + constexpr void _On_zero() { + // Note 0 is again not valid for CharT or bool unless a numeric + // presentation type is uesd. + _Numeric_checker._Require_numeric_argument(); + _Handler::_On_zero(); } -} + + constexpr void _On_precision(unsigned int _Precision) { + _Numeric_checker._Check_precision(); + _Handler::_On_precision(_Precision); + } +}; template /* consteval */ constexpr auto _Get_format_arg_storage_type() noexcept { @@ -745,6 +1085,10 @@ public: template using formatter_type = formatter<_Ty, _CharT>; + constexpr basic_format_context( + _Out _OutputIt_, basic_format_args _Ctx_args, const locale& _Loc_) + : _OutputIt(_OutputIt_), _Args(_Ctx_args), _Loc(_Loc_) {} + basic_format_arg arg(size_t _Id) const { return _Args.get(_Id); } @@ -759,6 +1103,121 @@ public: // TODO: IDL support probably required _OutputIt = _It; } + + const basic_format_args& _Get_args() const { + return _Args; + } +}; + +template +_OutputIt _Write(_OutputIt _Out, monostate) { + _STL_INTERNAL_CHECK(false); + return _Out; +} + +template +_OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { + if (!_Value) { + throw format_error("String pointer is null."); + } + while (*_Value) { + *_Out++ = *_Value++; + } + return _Out; +} + +template +_OutputIt _Write(_OutputIt _Out, _Ty _Val) { + (void) _Val; + _STL_INTERNAL_CHECK(false); + return _Out; +} + +// Dispatcher to call enabled custom formatters, and do nothing otherwise. +template +class _Custom_formatter_dispatcher { +private: + using _Char_type = typename _Context::char_type; + + basic_format_parse_context<_Char_type>& _Parse_ctx; + _Context& _Ctx; + +public: + explicit constexpr _Custom_formatter_dispatcher( + basic_format_parse_context<_Char_type>& _Parse_ctx_, _Context& _Ctx_) + : _Parse_ctx(_Parse_ctx_), _Ctx(_Ctx_) {} + + void operator()(typename basic_format_arg<_Context>::handle _Handle) const { + _Handle.format(_Parse_ctx, _Ctx); + } + + template + void operator()(_Ty) const {} +}; + +// This is the visitor that's used for "simple" replacement fields, +// it could be a generic lambda (with overloaded), but that's +// bad for throughput. A simple replacement field is a replacement field +// that's just "{}", without any format specs. +template +struct _Default_arg_formatter { + using _Context = basic_format_context<_OutputIt, _CharT>; + + _OutputIt _Out; + basic_format_args<_Context> _Args; + locale _Loc; + + template + _OutputIt operator()(_Ty _Val) { + return _Write<_CharT>(_Out, _Val); + } + + _OutputIt operator()(typename basic_format_arg<_Context>::handle _Handle) { + basic_format_parse_context<_CharT> _Parse_ctx({}); + basic_format_context<_OutputIt, _CharT> _Format_ctx(_Out, _Args, _Loc); + _Handle.format(_Parse_ctx, _Format_ctx); + return _Format_ctx.out(); + } +}; + +// The top level set of parsing "actions". +template +struct _Format_handler { + basic_format_parse_context<_CharT> _Parse_context; + _Context _Ctx; + + explicit _Format_handler( + _OutputIt _Out, basic_string_view<_CharT> _Str, basic_format_args<_Context> _Format_args, const locale& _Loc) + : _Parse_context(_Str), _Ctx(_Out, _Format_args, _Loc) {} + + void _On_text(const _CharT* _Begin, const _CharT* _End) { + auto _Size = _End - _Begin; + auto _Out = _Ctx.out(); + _Out = _STD copy_n(_Begin, _Size, _Out); + _Ctx.advance_to(_Out); + } + + void _On_replacement_field(size_t _Id, const _CharT*) { + auto _Arg = _Ctx.arg(_Id); + _Ctx.advance_to(visit_format_arg( + _Default_arg_formatter<_OutputIt, _CharT>{_Ctx.out(), _Ctx._Get_args(), _Ctx.locale()}, _Arg)); + } + + const _CharT* _On_format_specs(size_t _Id, const _CharT* _Begin, const _CharT* _End) { + _Parse_context.advance_to(_Parse_context.begin() + (_Begin - &*_Parse_context.begin())); + auto _Arg = _Ctx.arg(_Id); + _Basic_format_specs<_CharT> _Specs; + _Specs_checker<_Specs_handler, _Context>> _Handler( + _Specs_handler, _Context>(_Specs, _Parse_context, _Ctx), + _Arg._Active_state); + _Begin = _Parse_format_specs(_Begin, _End, _Handler); + if (_Begin == _End || *_Begin != '}') { + throw format_error("Missing '}' in format string."); + } + // TODO: implement format spec dispatching. + _STL_INTERNAL_CHECK(false); + return _Begin; + } }; using format_context = basic_format_context, string::value_type>; @@ -776,6 +1235,17 @@ auto make_wformat_args(const _Args&... _Vals) { return _Format_arg_store<_Context, _Args...>{_Vals...}; } +template +using format_args_t = basic_format_args>; + +template +_Out vformat_to( + _Out _OutputIt, const locale& _Loc, string_view _Fmt, format_args_t, char> _Args) { + _Format_handler<_Out, char, basic_format_context<_Out, char>> _Handler(_OutputIt, _Fmt, _Args, _Loc); + _Parse_format_string(_Fmt, _Handler); + return _Handler._Ctx.out(); +} + // FUNCTION vformat string vformat(string_view _Fmt, format_args _Args); wstring vformat(wstring_view _Fmt, wformat_args _Args); diff --git a/tests/std/test.lst b/tests/std/test.lst index e179bf9671e..d71e3493b8d 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -255,6 +255,7 @@ tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants tests\P0645R10_text_formatting_args tests\P0645R10_text_formatting_death +tests\P0645R10_text_formatting_formatting tests\P0645R10_text_formatting_parse_contexts tests\P0645R10_text_formatting_parsing tests\P0660R10_jthread_and_cv_any diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/env.lst b/tests/std/tests/P0645R10_text_formatting_formatting/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_formatting/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_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp new file mode 100644 index 00000000000..3c0f0d9173d --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +// TODO: fill in tests +template std::back_insert_iterator std::vformat_to(std::back_insert_iterator, + const std::locale&, std::string_view, std::format_args_t, char>); + + +int main() {} diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 85da914a9ac..9340bf3f3fa 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -30,16 +30,32 @@ struct choose_literal { #define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) +template +struct noop_testing_callbacks { + constexpr void _On_align(_Align) {} + constexpr void _On_fill(basic_string_view) {} + constexpr void _On_width(unsigned int) {} + constexpr void _On_dynamic_width(size_t) {} + constexpr void _On_dynamic_width(_Auto_id_tag) {} + constexpr void _On_precision(unsigned int) {} + constexpr void _On_dynamic_precision(size_t) {} + constexpr void _On_dynamic_precision(_Auto_id_tag) {} + constexpr void _On_sign(_Sign) {} + constexpr void _On_hash() {} + constexpr void _On_zero() {} + constexpr void _On_type(CharT) {} +}; + template struct testing_callbacks { _Align expected_alignment = _Align::_None; _Sign expected_sign = _Sign::_None; basic_string_view expected_fill; - int expected_width = -1; - int expected_dynamic_width = -1; + unsigned int expected_width = static_cast(-1); + size_t expected_dynamic_width = static_cast(-1); bool expected_auto_dynamic_width = false; - int expected_precision = -1; - int expected_dynamic_precision = -1; + unsigned int expected_precision = static_cast(-1); + size_t expected_dynamic_precision = static_cast(-1); bool expected_auto_dynamic_precision = false; bool expected_hash = false; bool expected_zero = false; @@ -50,19 +66,19 @@ struct testing_callbacks { constexpr void _On_fill(basic_string_view str_view) { assert(str_view == expected_fill); } - constexpr void _On_width(int width) { + constexpr void _On_width(unsigned int width) { assert(width == expected_width); } - constexpr void _On_dynamic_width(int id) { + constexpr void _On_dynamic_width(size_t id) { assert(id == expected_dynamic_width); } constexpr void _On_dynamic_width(_Auto_id_tag) { assert(expected_auto_dynamic_width); } - constexpr void _On_precision(int pre) { + constexpr void _On_precision(unsigned int pre) { assert(pre == expected_precision); } - constexpr void _On_dynamic_precision(int id) { + constexpr void _On_dynamic_precision(size_t id) { assert(id == expected_dynamic_precision); } constexpr void _On_dynamic_precision(_Auto_id_tag) { @@ -86,7 +102,7 @@ testing_callbacks(_Align, basic_string_view) -> testing_callbacks; struct testing_arg_id_callbacks { constexpr void _On_auto_id() {} - constexpr void _On_manual_id(int) {} + constexpr void _On_manual_id(size_t) {} }; template @@ -262,6 +278,24 @@ constexpr bool test_parse_format_specs() { return true; } +template +constexpr bool test_specs_setter() { + // just instantiate for now. + _Basic_format_specs specs = {}; + _Specs_setter setter(specs); + + (void) setter; + return true; +} + +template +constexpr bool test_specs_checker() { + _Specs_checker> checker( + noop_testing_callbacks{}, _Basic_format_arg_type::_Float_type); + (void) checker; + return true; +} + int main() { test_parse_align(); test_parse_align(); @@ -288,5 +322,15 @@ int main() { static_assert(test_parse_format_specs()); static_assert(test_parse_format_specs()); + test_specs_setter(); + test_specs_setter(); + static_assert(test_specs_setter()); + static_assert(test_specs_setter()); + + test_specs_checker(); + test_specs_checker(); + static_assert(test_specs_checker()); + static_assert(test_specs_checker()); + return 0; }