Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dab5e89
add format header
barcharcraz Jun 16, 2020
f98520f
add tests to build
barcharcraz Jun 26, 2020
b34c42a
add format_arg_value and custom_value.
barcharcraz Jul 1, 2020
ef225c1
more format parser
barcharcraz Aug 10, 2020
035bc05
parse align
barcharcraz Aug 13, 2020
8def328
fill/align parsing tests.
barcharcraz Aug 21, 2020
6209a57
arg_id and width
barcharcraz Aug 24, 2020
7c61ccc
tests for arg_id, quite basic right now
barcharcraz Aug 26, 2020
cbaf074
correct a spelling error
barcharcraz Aug 26, 2020
52f0f83
add <format> to other required files
barcharcraz Sep 1, 2020
30c11a6
resolve some review comments
barcharcraz Sep 2, 2020
0ced949
respond to review comments
barcharcraz Sep 8, 2020
e167e86
start conversion to string_view
barcharcraz Sep 8, 2020
45a09b3
some tests for width
barcharcraz Sep 9, 2020
02060dc
enable wchar_t
barcharcraz Sep 9, 2020
d6a16ed
constexprify tests.
barcharcraz Sep 9, 2020
14f0b50
tests for non-parsing is todo
barcharcraz Sep 10, 2020
85c0d34
forgot a bit of named args that needed removal.
barcharcraz Sep 10, 2020
fcd6ea0
remove a requires clause and use brief syntax.
barcharcraz Sep 10, 2020
7d95919
newlines
barcharcraz Sep 10, 2020
6da6a40
use concepts_matrix.
barcharcraz Sep 10, 2020
6cd007d
some review comments were hiding from me
barcharcraz Sep 14, 2020
9c323ee
fix wchar_t misparse bug
barcharcraz Sep 14, 2020
cd9c284
Apply suggestions from code review
Sep 21, 2020
735d757
Apply suggestions from code review
Sep 21, 2020
29367f7
move test coverage todo
barcharcraz Sep 21, 2020
af1381e
more review comments
barcharcraz Sep 22, 2020
511eb2c
fix test string numbering
barcharcraz Sep 22, 2020
412e513
comment on narrowing test case
barcharcraz Sep 22, 2020
72f6cac
fix minor issues
barcharcraz Sep 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions stl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions stl/inc/__msvc_all_public_headers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include <deque>
#include <exception>
#include <filesystem>
#include <format>
#include <forward_list>
#include <fstream>
#include <functional>
Expand Down
357 changes: 357 additions & 0 deletions stl/inc/format
Original file line number Diff line number Diff line change
@@ -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 <yvals_core.h>
#if _STL_COMPILER_PREPROCESSOR
#ifndef __cpp_lib_concepts
#pragma message("The contents of <format> are available only with C++20 concepts support.")
#else // ^^^ !defined(__cpp_lib_concepts) / defined(__cpp_lib_concepts) vvv

#include <charconv>
#include <concepts>
#include <exception>
#include <iterator>
#include <locale>
#include <string>
#include <string_view>
#include <variant>

#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 <class _Ty, class _CharT>
concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) {
{ _At._On_align(_Aln) } -> same_as<void>;
{ _At._On_fill(_Sv) } -> same_as<void>;
{ _At._On_width(_STD declval<int>()) } -> same_as<void>;
{ _At._On_dynamic_width(_STD declval<int>()) } -> same_as<void>;
{ _At._On_dynamic_width(_STD declval<_Auto_id_tag>()) } -> same_as<void>;
};
template <class _Ty, class _CharT>
concept _Parse_arg_id_callbacks = requires(_Ty _At) {
{ _At._On_auto_id() } -> same_as<void>;
{ _At._On_manual_id(_STD declval<int>()) } -> same_as<void>;
};
// clang-format on

// we need to implement this ourselves because from_chars does not work with wide characters
template <class _CharT>
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<unsigned int>((numeric_limits<int>::max)());
constexpr unsigned int _Big_int = _Max_int / 10;

do {
if (_Value > _Big_int) {
_Value = _Max_int + 1;
break;
}
_Value = _Value * 10 + static_cast<unsigned int>(*_Begin - '0');
++_Begin;
} while (_Begin != _End && '0' <= *_Begin && *_Begin <= '9');
if (_Value > _Max_int) {
throw format_error("Number is too big");
}
_Integer = static_cast<int>(_Value);
return _Begin;
}

template <class _CharT, _Parse_arg_id_callbacks<_CharT> _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 <class _CharT, _Parse_spec_callbacks<_CharT> _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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefere to handle the error case here in a default. essentially anything else that comes here is an error. This should simplify the control flow below considerably

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's actually not true, since you're free to just ... not have an alignment, _Parse_align doesn't see anything that looks like an alignment it doesn't fail, the only error here is if you try and get a {} into the fill character.

I will admit that this parse isn't that great at rejecting invalid format strings, that's just a fact of stealing libfmts parsing logic.

}
if (_Parsed_align != _Align::_None) {
if (_Align_pt != _Begin) {
if (*_Begin == '{') {
throw format_error("invalid fill character '{'");
}
_Callbacks._On_fill({_Begin, static_cast<size_t>(_Align_pt - _Begin)});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concept passes a basic_string_view<_CharT> to _On_fill, but this is an untyped braced-init-list; we didn't require this call to be valid.

Copy link
Contributor

@CaseyCarter CaseyCarter Aug 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't _Align_pt - _Begin always 1 here? (justified elsewhere).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you suggest I do here?

_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 <class _CharT, _Parse_spec_callbacks<_CharT> _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 <class _CharT, _Parse_spec_callbacks<_CharT> _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 <class _Ty, class _CharT = char>
struct formatter;

// TODO: test coverage
template <class _CharT>
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 _Context>
class basic_format_arg {
public:
class handle;

private:
using _Char_type = typename _Context::char_type;

using _Format_arg_value = variant<monostate, int, unsigned, long long, unsigned long long, bool,
typename _Context::char_type, float, double, long double, const void*, const typename _Context::char_type,
basic_string_view<typename _Context::char_type>, 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<monostate>(_Value);
}
};

// TODO: test coverage
template <class _Context, class... _Args>
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 _Context>
class basic_format_args {
public:
basic_format_args() noexcept;
template <class... _Args>
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 <class _Out, class _CharT>
requires output_iterator<_Out, _CharT>
class basic_format_context {
// clang-format on
private:
_Out _OutputIt;
basic_format_args<basic_format_context> _Args;
locale _Loc;

public:
using iterator = _Out;
using char_type = _CharT;

template <class _Ty>
using formatter_type = formatter<_Ty, _CharT>;

basic_format_arg<basic_format_context> 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<back_insert_iterator<string>, string::value_type>;
using wformat_context = basic_format_context<back_insert_iterator<wstring>, wstring::value_type>;
using format_args = basic_format_args<format_context>;
using wformat_args = basic_format_args<wformat_context>;

// 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_
2 changes: 2 additions & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
Loading