Skip to content

Commit 408680d

Browse files
authored
Merge pull request #23 from rsl-org/feature/kwargs
feature/kwargs
2 parents 7311d76 + 7edd631 commit 408680d

9 files changed

Lines changed: 382 additions & 23 deletions

File tree

include/rsl/_format_impl/fmt_parser.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
#include <rsl/span>
1111

1212
#include <rsl/util/to_string.hpp>
13+
#include <rsl/_impl/parser.hpp>
1314

1415
#include "accessor.hpp"
15-
#include "parser.hpp"
1616
#include "style.hpp"
1717
#include "util.hpp"
1818

@@ -90,15 +90,15 @@ enum struct ReplacementType : std::uint8_t {
9090
field = 4
9191
};
9292

93-
struct Replacement final : Parser {
93+
struct Replacement final : _impl::Parser {
9494
ReplacementType kind = ReplacementType::invalid;
9595
std::string field;
9696
std::string specs;
9797
std::vector<std::string> style_tags;
9898

9999
std::size_t index = -1ZU;
100100

101-
using Parser::Parser;
101+
using _impl::Parser::Parser;
102102

103103
constexpr void add_style(FormatString& out, bool enable) const {
104104
if (!style_tags.empty()) {
@@ -177,7 +177,7 @@ struct Replacement final : Parser {
177177
// error
178178
}
179179
} else {
180-
kind = std::ranges::all_of(field, is_digit) //
180+
kind = std::ranges::all_of(field, _impl::is_digit) //
181181
? ReplacementType::indexed
182182
: ReplacementType::field;
183183
}
@@ -188,7 +188,7 @@ struct Replacement final : Parser {
188188
for (auto subfield : std::views::split(field, '.')) {
189189
type = remove_cvref(type);
190190
std::meta::info accessor{};
191-
if (std::ranges::all_of(subfield, is_digit)) {
191+
if (std::ranges::all_of(subfield, _impl::is_digit)) {
192192
index = util::stou(std::string_view{subfield});
193193
if (is_subscriptable(type)) {
194194
// member access by subscript operator
@@ -218,7 +218,7 @@ struct Replacement final : Parser {
218218
}
219219
};
220220

221-
struct FormatParser : Parser {
221+
struct FormatParser : _impl::Parser {
222222
using Parser::Parser;
223223

224224
FormatString result;
@@ -283,7 +283,7 @@ struct FormatParser : Parser {
283283
case ReplacementType::field: {
284284
has_positional = true;
285285
auto dot_pos = replacement.field.find('.');
286-
if (dot_pos == replacement.field.npos || !is_digit(replacement.field[0])) {
286+
if (dot_pos == replacement.field.npos || !_impl::is_digit(replacement.field[0])) {
287287
if (arg_types.size() != 1) {
288288
// todo error
289289
throw "implicit 0. not allowed with more than one arg";

include/rsl/_impl/member_cache.hpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
#include <utility>
44
#include <memory>
55
#include <meta>
6+
#include <algorithm>
7+
#include <ranges>
8+
69

710
namespace rsl::_impl {
8-
template <auto... Members>
11+
template <std::meta::info... Members>
912
struct MemberAccessor {
1013
template <std::size_t Idx, typename S>
1114
[[gnu::always_inline]]
@@ -19,8 +22,8 @@ struct MemberAccessor {
1922
return std::addressof(std::forward<S>(storage).[:Members...[Idx]:]);
2023
}
2124

22-
constexpr static std::array<std::meta::info, sizeof...(Members)> types = {dealias(type_of(Members))...};
23-
constexpr static auto count = sizeof...(Members);
25+
constexpr static auto count = sizeof...(Members);
26+
constexpr static std::array<std::meta::info, count> types = {dealias(type_of(Members))...};
2427

2528
static consteval std::size_t get_index_of(std::meta::info needle) {
2629
constexpr std::size_t npos = ~0ZU;
@@ -38,6 +41,24 @@ struct MemberAccessor {
3841
}
3942
return selected;
4043
}
44+
45+
static consteval std::size_t get_index_of(std::string_view name) {
46+
std::vector<std::string_view> names;
47+
names.reserve(count);
48+
for (auto member : std::vector<std::meta::info>{Members...}) {
49+
if (not has_identifier(member)) { continue; }
50+
names.push_back(identifier_of(member));
51+
}
52+
53+
if (auto it = std::ranges::find(names, name); it != names.end()) {
54+
return std::distance(names.begin(), it);
55+
}
56+
return -1UZ;
57+
}
58+
59+
static consteval bool has_member(std::string_view name) {
60+
return get_index_of(name) != -1UZ;
61+
}
4162
};
4263

4364
template <auto... Members>
@@ -50,4 +71,12 @@ consteval auto cache_members(auto&& member_range) {
5071
}
5172
return substitute(^^member_cache, members);
5273
}
74+
75+
consteval auto cache_members_t(auto&& member_range) {
76+
std::vector<std::meta::info> members;
77+
for (auto member : member_range) {
78+
members.push_back(std::meta::reflect_constant(member));
79+
}
80+
return substitute(^^MemberAccessor, members);
81+
};
5382
} // namespace rsl::_impl
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
#include <algorithm>
44
#include <concepts>
55

6-
//TODO move to _impl, reuse for kwargs?
7-
namespace rsl::_format_impl {
6+
namespace rsl::_impl {
87

98
constexpr bool is_whitespace(char c) {
109
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
@@ -29,7 +28,7 @@ struct Parser {
2928
std::string_view data;
3029
int cursor = 0;
3130

32-
constexpr Parser(std::string_view data) : data(data) {}
31+
explicit constexpr Parser(std::string_view data) : data(data) {}
3332

3433
constexpr bool is_valid() const { return cursor >= 0 && cursor < data.size(); }
3534
constexpr char current() const { return is_valid() ? data[cursor] : '\0'; }
@@ -60,9 +59,9 @@ struct Parser {
6059
while (is_valid()) {
6160
if (char c = current(); brace_count == 0 && ((c == needles) || ...)) {
6261
break;
63-
} else if (c == '{') {
62+
} else if (c == '[' || c == '{' || c == '(') {
6463
++brace_count;
65-
} else if (c == '}') {
64+
} else if (c == ']' || c == '}' || c == ')') {
6665
--brace_count;
6766
}
6867
++cursor;

include/rsl/kwargs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#pragma once
2+
#include <algorithm>
3+
#include <meta>
4+
#include <string_view>
5+
#include <vector>
6+
#include <tuple>
7+
#include <type_traits>
8+
#include <utility>
9+
#include <ranges>
10+
#include <cstddef>
11+
12+
#include <rsl/string_constant>
13+
#include <rsl/_impl/parser.hpp>
14+
#include <rsl/_impl/member_cache.hpp>
15+
16+
namespace rsl {
17+
18+
template <typename Impl>
19+
struct [[nodiscard]] kwargs_t : Impl {
20+
using type = Impl;
21+
static constexpr auto _member_cache = [:_impl::cache_members(nonstatic_data_members_of(
22+
^^Impl,
23+
std::meta::access_context::unchecked())):];
24+
};
25+
26+
template <typename T>
27+
concept is_kwargs = has_template_arguments(^^T) && template_of(^^T) == ^^kwargs_t;
28+
29+
namespace kwargs {
30+
struct NameParser : _impl::Parser {
31+
using _impl::Parser::Parser;
32+
33+
std::vector<std::string_view> names;
34+
35+
constexpr bool parse() {
36+
cursor = 0;
37+
38+
while (is_valid()) {
39+
skip_whitespace();
40+
41+
if (current() == '&') {
42+
// might be captured by reference
43+
++cursor;
44+
skip_whitespace();
45+
}
46+
47+
if (current() == '.') {
48+
// pack captured, reject
49+
return false;
50+
}
51+
52+
std::size_t start = cursor;
53+
54+
// find '=', ',' or whitespace
55+
skip_to('=', ',', ' ', '\n', '\r', '\t');
56+
if (cursor - start == 0) {
57+
// default capture or invalid name
58+
return false;
59+
}
60+
61+
auto name = data.substr(start, cursor - start);
62+
if (name == "this" || name == "*this") {
63+
// this captured, reject
64+
return false;
65+
}
66+
names.emplace_back(name);
67+
68+
// skip ahead to next capture
69+
// if the current character is already ',', this will not move the cursor
70+
skip_to(',');
71+
++cursor;
72+
73+
skip_whitespace();
74+
}
75+
return true;
76+
}
77+
};
78+
79+
template <string_constant Names, typename... Ts>
80+
constexpr auto make(Ts&&... values) {
81+
struct kwargs_impl;
82+
consteval {
83+
std::vector<std::meta::info> types{^^Ts...};
84+
std::vector<std::meta::info> args;
85+
86+
auto parser = NameParser(Names);
87+
88+
// with P3068 parser.parse() could throw to provide better diagnostics at this point
89+
if (!parser.parse()) {
90+
return;
91+
}
92+
93+
// associate every argument with the corresponding name
94+
// retrieved by parsing the capture list
95+
96+
// std::views::zip_transform could also be used for this
97+
for (auto [member, name] : std::views::zip(types, parser.names)) {
98+
args.push_back(data_member_spec(member, {.name = name}));
99+
}
100+
define_aggregate(^^kwargs_impl, args);
101+
};
102+
103+
// ensure injecting the class worked
104+
static_assert(is_type(^^kwargs_impl), std::string{"Invalid keyword arguments `"} + Names + "`");
105+
106+
return kwargs_t<kwargs_impl>{{std::forward<Ts>(values)...}};
107+
}
108+
} // namespace kwargs
109+
110+
template <string_constant Names, typename... Ts>
111+
constexpr auto make_args(Ts&&... values) {
112+
return kwargs::make<Names>(std::forward<Ts>(values)...);
113+
}
114+
115+
template <typename T>
116+
requires is_kwargs<std::remove_cvref_t<T>>
117+
consteval bool has_arg(std::string_view name) {
118+
return std::remove_cvref_t<T>::_member_cache.has_member(name);
119+
}
120+
121+
// get
122+
123+
template <std::size_t Idx, typename T>
124+
requires is_kwargs<std::remove_cvref_t<T>>
125+
constexpr auto get(T&& kwargs) noexcept {
126+
static_assert(std::remove_cvref_t<T>::_member_cache.count > Idx);
127+
return std::remove_cvref_t<T>::_member_cache.template get<Idx>(std::forward<T>(kwargs));
128+
}
129+
130+
template <string_constant name, typename T>
131+
requires is_kwargs<std::remove_cvref_t<T>>
132+
constexpr auto get(T&& kwargs) {
133+
static_assert(has_arg<T>(name), "Keyword argument `" + std::string(name) + "` not found.");
134+
135+
static constexpr auto Idx = std::remove_cvref_t<T>::_member_cache.get_index_of(name);
136+
return get<Idx>(std::forward<T>(kwargs));
137+
}
138+
139+
// get_or
140+
template <std::size_t Idx, typename T, typename R>
141+
requires is_kwargs<std::remove_cvref_t<T>>
142+
constexpr auto get_or(T&& kwargs, R default_) noexcept {
143+
using kwarg_tuple = std::remove_cvref_t<T>;
144+
if constexpr (kwarg_tuple::_member_cache.count > Idx) {
145+
return get<Idx>(std::forward<T>(kwargs));
146+
} else {
147+
return default_;
148+
}
149+
}
150+
151+
template <string_constant name, typename T, typename R>
152+
requires is_kwargs<std::remove_cvref_t<T>>
153+
constexpr auto get_or(T&& kwargs, R default_) {
154+
using kwarg_tuple = std::remove_cvref_t<T>;
155+
if constexpr (kwarg_tuple::_member_cache.count >
156+
std::remove_cvref_t<T>::_member_cache.get_index_of(name)) {
157+
return get<name>(std::forward<T>(kwargs));
158+
} else {
159+
return default_;
160+
}
161+
}
162+
} // namespace rsl
163+
164+
template <typename T>
165+
struct std::tuple_size<rsl::kwargs_t<T>>
166+
: public integral_constant<size_t, rsl::kwargs_t<std::remove_cvref_t<T>>::_member_cache.count> {
167+
};
168+
169+
template <std::size_t Idx, typename T>
170+
struct std::tuple_element<Idx, rsl::kwargs_t<T>> {
171+
using type = [:rsl::kwargs_t<std::remove_cvref_t<T>>::_member_cache.types[Idx]:];
172+
};
173+
174+
#define RSL_KWARGS(...) \
175+
[__VA_ARGS__]<typename T>(this T _impl_this) { \
176+
constexpr static auto _impl_captures = \
177+
define_static_array(nonstatic_data_members_of(^^T, std::meta::access_context::current())); \
178+
return [&]<std::size_t... Idx>(std::index_sequence<Idx...>) { \
179+
return rsl::kwargs::make<#__VA_ARGS__>( \
180+
std::forward<decltype(_impl_this.[:_impl_captures[Idx]:])>( \
181+
_impl_this.[:_impl_captures[Idx]:])...); \
182+
}(std::make_index_sequence<_impl_captures.size()>()); \
183+
}()
184+
185+
#define $args(...) RSL_KWARGS(__VA_ARGS__)

include/rsl/string_constant

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
#pragma once
2+
#include <cstddef>
3+
#include <string_view>
4+
#include <algorithm>
25

36
namespace rsl {
47
template <std::size_t N>
58
struct string_constant {
69
constexpr static auto size = N;
10+
char data[N + 1]{};
711

812
consteval string_constant() = default;
913
consteval explicit(false) string_constant(const char (&str)[N + 1]) noexcept {
10-
auto idx = std::size_t{0};
11-
for (char const chr : str) {
12-
data[idx++] = chr;
13-
}
14+
std::ranges::copy(str, str + N, data);
1415
}
15-
consteval explicit string_constant(std::same_as<char> auto... Vs) : data{Vs...} {}
16-
consteval explicit string_constant(std::string_view str) { str.copy(data, N); }
17-
constexpr explicit(false) operator std::string_view() const { return std::string_view{data}; }
1816

19-
char data[N + 1]{};
17+
consteval explicit string_constant(std::same_as<char> auto... Vs)
18+
requires(sizeof...(Vs) <= N)
19+
: data{Vs...} {}
20+
21+
consteval explicit string_constant(std::string_view str) {
22+
str.copy(data, std::min(str.size(), N));
23+
}
24+
constexpr explicit(false) operator std::string_view() const { return std::string_view{data}; }
2025
};
2126

2227
template <std::size_t N>

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ add_subdirectory(serializer)
99
add_subdirectory(string_view)
1010
add_subdirectory(span)
1111
add_subdirectory(format)
12+
add_subdirectory(kwargs)

test/kwargs/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
target_sources(rsl-util-test PRIVATE
2+
capture_list.cpp
3+
simple.cpp
4+
)

0 commit comments

Comments
 (0)