]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | // Formatting library for C++ - scanning API proof of concept |
2 | // | |
3 | // Copyright (c) 2019 - present, Victor Zverovich | |
4 | // All rights reserved. | |
5 | // | |
6 | // For the license information refer to format.h. | |
7 | ||
8 | #include <array> | |
9 | #include <cassert> | |
10 | #include <climits> | |
11 | ||
12 | #include "fmt/format.h" | |
13 | ||
14 | FMT_BEGIN_NAMESPACE | |
15 | template <typename T, typename Char = char> struct scanner { | |
16 | // A deleted default constructor indicates a disabled scanner. | |
17 | scanner() = delete; | |
18 | }; | |
19 | ||
20 | class scan_parse_context { | |
21 | private: | |
22 | string_view format_; | |
23 | ||
24 | public: | |
25 | using iterator = string_view::iterator; | |
26 | ||
27 | explicit FMT_CONSTEXPR scan_parse_context(string_view format) | |
28 | : format_(format) {} | |
29 | ||
30 | FMT_CONSTEXPR iterator begin() const { return format_.begin(); } | |
31 | FMT_CONSTEXPR iterator end() const { return format_.end(); } | |
32 | ||
33 | void advance_to(iterator it) { | |
34 | format_.remove_prefix(detail::to_unsigned(it - begin())); | |
35 | } | |
36 | }; | |
37 | ||
38 | struct scan_context { | |
39 | private: | |
40 | string_view input_; | |
41 | ||
42 | public: | |
43 | using iterator = const char*; | |
44 | ||
45 | explicit scan_context(string_view input) : input_(input) {} | |
46 | ||
47 | iterator begin() const { return input_.data(); } | |
48 | iterator end() const { return begin() + input_.size(); } | |
49 | ||
50 | void advance_to(iterator it) { | |
51 | input_.remove_prefix(detail::to_unsigned(it - begin())); | |
52 | } | |
53 | }; | |
54 | ||
55 | namespace detail { | |
56 | enum class scan_type { | |
57 | none_type, | |
58 | int_type, | |
59 | uint_type, | |
60 | long_long_type, | |
61 | ulong_long_type, | |
62 | string_type, | |
63 | string_view_type, | |
64 | custom_type | |
65 | }; | |
66 | ||
67 | struct custom_scan_arg { | |
68 | void* value; | |
69 | void (*scan)(void* arg, scan_parse_context& parse_ctx, scan_context& ctx); | |
70 | }; | |
71 | ||
72 | class scan_arg { | |
73 | public: | |
74 | scan_type type; | |
75 | union { | |
76 | int* int_value; | |
77 | unsigned* uint_value; | |
78 | long long* long_long_value; | |
79 | unsigned long long* ulong_long_value; | |
80 | std::string* string; | |
81 | fmt::string_view* string_view; | |
82 | custom_scan_arg custom; | |
83 | // TODO: more types | |
84 | }; | |
85 | ||
86 | scan_arg() : type(scan_type::none_type) {} | |
87 | scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {} | |
88 | scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {} | |
89 | scan_arg(long long& value) | |
90 | : type(scan_type::long_long_type), long_long_value(&value) {} | |
91 | scan_arg(unsigned long long& value) | |
92 | : type(scan_type::ulong_long_type), ulong_long_value(&value) {} | |
93 | scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {} | |
94 | scan_arg(fmt::string_view& value) | |
95 | : type(scan_type::string_view_type), string_view(&value) {} | |
96 | template <typename T> scan_arg(T& value) : type(scan_type::custom_type) { | |
97 | custom.value = &value; | |
98 | custom.scan = scan_custom_arg<T>; | |
99 | } | |
100 | ||
101 | private: | |
102 | template <typename T> | |
103 | static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx, | |
104 | scan_context& ctx) { | |
105 | scanner<T> s; | |
106 | parse_ctx.advance_to(s.parse(parse_ctx)); | |
107 | ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx)); | |
108 | } | |
109 | }; | |
110 | } // namespace detail | |
111 | ||
112 | struct scan_args { | |
113 | int size; | |
114 | const detail::scan_arg* data; | |
115 | ||
116 | template <size_t N> | |
117 | scan_args(const std::array<detail::scan_arg, N>& store) | |
118 | : size(N), data(store.data()) { | |
119 | static_assert(N < INT_MAX, "too many arguments"); | |
120 | } | |
121 | }; | |
122 | ||
123 | namespace detail { | |
124 | ||
125 | struct scan_handler : error_handler { | |
126 | private: | |
127 | scan_parse_context parse_ctx_; | |
128 | scan_context scan_ctx_; | |
129 | scan_args args_; | |
130 | int next_arg_id_; | |
131 | scan_arg arg_; | |
132 | ||
133 | template <typename T = unsigned> T read_uint() { | |
134 | T value = 0; | |
135 | auto it = scan_ctx_.begin(), end = scan_ctx_.end(); | |
136 | while (it != end) { | |
137 | char c = *it++; | |
138 | if (c < '0' || c > '9') on_error("invalid input"); | |
139 | // TODO: check overflow | |
140 | value = value * 10 + static_cast<unsigned>(c - '0'); | |
141 | } | |
142 | scan_ctx_.advance_to(it); | |
143 | return value; | |
144 | } | |
145 | ||
146 | template <typename T = int> T read_int() { | |
147 | auto it = scan_ctx_.begin(), end = scan_ctx_.end(); | |
148 | bool negative = it != end && *it == '-'; | |
149 | if (negative) ++it; | |
150 | scan_ctx_.advance_to(it); | |
151 | const auto value = read_uint<typename std::make_unsigned<T>::type>(); | |
152 | if (negative) return -static_cast<T>(value); | |
153 | return static_cast<T>(value); | |
154 | } | |
155 | ||
156 | public: | |
157 | scan_handler(string_view format, string_view input, scan_args args) | |
158 | : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {} | |
159 | ||
160 | const char* pos() const { return scan_ctx_.begin(); } | |
161 | ||
162 | void on_text(const char* begin, const char* end) { | |
163 | auto size = to_unsigned(end - begin); | |
164 | auto it = scan_ctx_.begin(); | |
165 | if (it + size > scan_ctx_.end() || | |
166 | !std::equal(begin, end, make_checked(it, size))) { | |
167 | on_error("invalid input"); | |
168 | } | |
169 | scan_ctx_.advance_to(it + size); | |
170 | } | |
171 | ||
20effc67 TL |
172 | FMT_CONSTEXPR int on_arg_id() { return on_arg_id(next_arg_id_++); } |
173 | FMT_CONSTEXPR int on_arg_id(int id) { | |
f67539c2 TL |
174 | if (id >= args_.size) on_error("argument index out of range"); |
175 | arg_ = args_.data[id]; | |
20effc67 TL |
176 | return id; |
177 | } | |
178 | FMT_CONSTEXPR int on_arg_id(string_view id) { | |
179 | if (id.data()) on_error("invalid format"); | |
180 | return 0; | |
f67539c2 | 181 | } |
f67539c2 | 182 | |
20effc67 | 183 | void on_replacement_field(int, const char*) { |
f67539c2 TL |
184 | auto it = scan_ctx_.begin(), end = scan_ctx_.end(); |
185 | switch (arg_.type) { | |
186 | case scan_type::int_type: | |
187 | *arg_.int_value = read_int(); | |
188 | break; | |
189 | case scan_type::uint_type: | |
190 | *arg_.uint_value = read_uint(); | |
191 | break; | |
192 | case scan_type::long_long_type: | |
193 | *arg_.long_long_value = read_int<long long>(); | |
194 | break; | |
195 | case scan_type::ulong_long_type: | |
196 | *arg_.ulong_long_value = read_uint<unsigned long long>(); | |
197 | break; | |
198 | case scan_type::string_type: | |
199 | while (it != end && *it != ' ') arg_.string->push_back(*it++); | |
200 | scan_ctx_.advance_to(it); | |
201 | break; | |
202 | case scan_type::string_view_type: { | |
203 | auto s = it; | |
204 | while (it != end && *it != ' ') ++it; | |
205 | *arg_.string_view = fmt::string_view(s, to_unsigned(it - s)); | |
206 | scan_ctx_.advance_to(it); | |
207 | break; | |
208 | } | |
209 | case scan_type::none_type: | |
210 | case scan_type::custom_type: | |
211 | assert(false); | |
212 | } | |
213 | } | |
214 | ||
20effc67 | 215 | const char* on_format_specs(int, const char* begin, const char*) { |
f67539c2 TL |
216 | if (arg_.type != scan_type::custom_type) return begin; |
217 | parse_ctx_.advance_to(begin); | |
218 | arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_); | |
219 | return parse_ctx_.begin(); | |
220 | } | |
221 | }; | |
222 | } // namespace detail | |
223 | ||
224 | template <typename... Args> | |
225 | std::array<detail::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) { | |
226 | return {{args...}}; | |
227 | } | |
228 | ||
229 | string_view::iterator vscan(string_view input, string_view format_str, | |
230 | scan_args args) { | |
231 | detail::scan_handler h(format_str, input, args); | |
232 | detail::parse_format_string<false>(format_str, h); | |
233 | return input.begin() + (h.pos() - &*input.begin()); | |
234 | } | |
235 | ||
236 | template <typename... Args> | |
237 | string_view::iterator scan(string_view input, string_view format_str, | |
238 | Args&... args) { | |
239 | return vscan(input, format_str, make_scan_args(args...)); | |
240 | } | |
241 | FMT_END_NAMESPACE |