-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add full support of format string parsing in compile-time API #2129
Changes from 6 commits
0059311
eb52e74
1556612
8b023e8
ba88399
c684b76
e8f696a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -454,15 +454,51 @@ template <typename Char, typename T, int N> struct field { | |
|
||
template <typename OutputIt, typename... Args> | ||
constexpr OutputIt format(OutputIt out, const Args&... args) const { | ||
// This ensures that the argument type is convertile to `const T&`. | ||
const T& arg = get<N>(args...); | ||
return write<Char>(out, arg); | ||
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { | ||
decltype(T::value) arg = get<N>(args...).value; | ||
return write<Char>(out, arg); | ||
} else { | ||
// This ensures that the argument type is convertile to `const T&`. | ||
const T& arg = get<N>(args...); | ||
return write<Char>(out, arg); | ||
} | ||
} | ||
}; | ||
|
||
template <typename Char, typename T, int N> | ||
struct is_compiled_format<field<Char, T, N>> : std::true_type {}; | ||
|
||
// A replacement field that refers to argument with name. | ||
template <typename Char> struct runtime_named_field { | ||
using char_type = Char; | ||
basic_string_view<Char> name; | ||
|
||
template <typename OutputIt, typename T> | ||
constexpr static bool try_format_argument(OutputIt& out, | ||
basic_string_view<Char> arg_name, | ||
const T& arg) { | ||
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { | ||
if (arg_name == arg.name) { | ||
alexezeder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
out = write<Char>(out, arg.value); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
template <typename OutputIt, typename... Args> | ||
constexpr OutputIt format(OutputIt out, const Args&... args) const { | ||
bool found = (try_format_argument(out, name, args) || ...); | ||
if (!found) { | ||
throw format_error("argument with specified name is not found"); | ||
} | ||
return out; | ||
} | ||
}; | ||
|
||
template <typename Char> | ||
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {}; | ||
|
||
// A replacement field that refers to argument N and has format specifiers. | ||
template <typename Char, typename T, int N> struct spec_field { | ||
using char_type = Char; | ||
|
@@ -536,15 +572,51 @@ template <typename T, typename Char> struct parse_specs_result { | |
int next_arg_id; | ||
}; | ||
|
||
constexpr int manual_indexing_id = -1; | ||
|
||
template <typename T, typename Char> | ||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, | ||
size_t pos, int arg_id) { | ||
size_t pos, int next_arg_id) { | ||
str.remove_prefix(pos); | ||
auto ctx = basic_format_parse_context<Char>(str, {}, arg_id + 1); | ||
auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id); | ||
auto f = formatter<T, Char>(); | ||
auto end = f.parse(ctx); | ||
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, | ||
ctx.next_arg_id()}; | ||
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; | ||
} | ||
|
||
template <typename Char> struct arg_id_handler { | ||
constexpr void on_error(const char* message) { throw format_error(message); } | ||
|
||
constexpr int on_arg_id() { | ||
FMT_ASSERT(false, "handler cannot be used with automatic indexing"); | ||
return 0; | ||
} | ||
|
||
constexpr int on_arg_id(int id) { | ||
arg_id = arg_ref<Char>(id); | ||
return 0; | ||
} | ||
|
||
constexpr int on_arg_id(basic_string_view<Char> id) { | ||
arg_id = arg_ref<Char>(id); | ||
return 0; | ||
} | ||
|
||
arg_ref<Char> arg_id; | ||
}; | ||
|
||
template <typename Char> struct parse_arg_id_result { | ||
arg_ref<Char> arg_id; | ||
const Char* arg_id_end; | ||
}; | ||
Comment on lines
+609
to
+612
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... it would be a reference to the pointer, or (IMHO better) a pointer to the pointer, is it ok? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I think reference is better unless it can be null. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, it's probably impossible because there is a need to have |
||
|
||
template <int ID, typename Char> | ||
constexpr auto parse_arg_id(const Char* begin, const Char* end) { | ||
auto handler = arg_id_handler<Char>{arg_ref<Char>{}}; | ||
auto adapter = id_adapter<arg_id_handler<Char>, Char>{handler, 0}; | ||
auto arg_id_end = parse_arg_id(begin, end, adapter); | ||
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end}; | ||
} | ||
|
||
// Compiles a non-empty format string and returns the compiled representation | ||
|
@@ -558,17 +630,52 @@ constexpr auto compile_format_string(S format_str) { | |
throw format_error("unmatched '{' in format string"); | ||
if constexpr (str[POS + 1] == '{') { | ||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); | ||
} else if constexpr (str[POS + 1] == '}') { | ||
using id_type = get_type<ID, Args>; | ||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, id_type, ID>(), | ||
format_str); | ||
} else if constexpr (str[POS + 1] == ':') { | ||
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { | ||
static_assert(ID != manual_indexing_id, | ||
"cannot switch from manual to automatic argument indexing"); | ||
using id_type = get_type<ID, Args>; | ||
constexpr auto result = parse_specs<id_type>(str, POS + 2, ID); | ||
return parse_tail<Args, result.end, result.next_arg_id>( | ||
spec_field<char_type, id_type, ID>{result.fmt}, format_str); | ||
if constexpr (str[POS + 1] == '}') { | ||
constexpr auto next_id = | ||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id; | ||
return parse_tail<Args, POS + 2, next_id>( | ||
field<char_type, id_type, ID>(), format_str); | ||
} else { | ||
constexpr auto result = parse_specs<id_type>(str, POS + 2, ID + 1); | ||
return parse_tail<Args, result.end, result.next_arg_id>( | ||
spec_field<char_type, id_type, ID>{result.fmt}, format_str); | ||
} | ||
} else { | ||
return unknown_format(); | ||
constexpr auto arg_id_result = | ||
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size()); | ||
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); | ||
constexpr char_type c = | ||
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); | ||
static_assert(c == '}' || c == ':', "missing '}' in format string"); | ||
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { | ||
static_assert( | ||
ID == manual_indexing_id || ID == 0, | ||
"cannot switch from automatic to manual argument indexing"); | ||
constexpr auto arg_index = arg_id_result.arg_id.val.index; | ||
using id_type = get_type<arg_index, Args>; | ||
if constexpr (c == '}') { | ||
return parse_tail<Args, arg_id_end_pos + 1, manual_indexing_id>( | ||
field<char_type, id_type, arg_index>(), format_str); | ||
} else if constexpr (c == ':') { | ||
constexpr auto result = | ||
parse_specs<id_type>(str, arg_id_end_pos + 1, 0); | ||
return parse_tail<Args, result.end, result.next_arg_id>( | ||
spec_field<char_type, id_type, arg_index>{result.fmt}, | ||
format_str); | ||
} | ||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { | ||
if constexpr (c == '}') { | ||
return parse_tail<Args, arg_id_end_pos + 1, ID>( | ||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, | ||
format_str); | ||
} else if constexpr (c == ':') { | ||
return unknown_format(); // no type info for specs parsing | ||
} | ||
} | ||
} | ||
} else if constexpr (str[POS] == '}') { | ||
if constexpr (POS + 1 == str.size()) | ||
|
@@ -670,8 +777,15 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&, | |
#ifdef __cpp_if_constexpr | ||
if constexpr (std::is_same<typename S::char_type, char>::value) { | ||
constexpr basic_string_view<typename S::char_type> str = S(); | ||
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') | ||
return fmt::to_string(detail::first(args...)); | ||
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { | ||
auto first = detail::first(args...); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may introduce an extra copy. Please use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
if constexpr (detail::is_named_arg<typename std::remove_cv< | ||
decltype(first)>::type>::value) { | ||
return fmt::to_string(first.value); | ||
} else { | ||
return fmt::to_string(first); | ||
} | ||
} | ||
} | ||
#endif | ||
constexpr auto compiled = detail::compile<Args...>(S()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
decltype(T::value)
can be replaced with a bit simplerconst auto&
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done