fix broken dependencies when building on aarch64
[benchmarks.git] / main.cpp
1 #include "harness.h"
2 #include <charconv>
3 #include <cstdlib>
4 #include <functional>
5 #include <initializer_list>
6 #include <iostream>
7 #include <map>
8 #include <optional>
9 #include <ostream>
10 #include <string_view>
11 #include <system_error>
12 #include <type_traits>
13 #include <vector>
14
15 using namespace std::literals;
16
17 enum class OptionValueKind
18 {
19 None,
20 Required,
21 };
22
23 class OptionsParser;
24
25 struct Option final
26 {
27 char short_name = '\0';
28 std::string_view long_name = "", description = "";
29 bool required = false;
30 bool all_other_args_not_required = false;
31 OptionValueKind value_kind = OptionValueKind::None;
32 std::function<void(OptionsParser &parser,
33 std::optional<std::string_view> value)>
34 parse_value;
35 bool has_short_name() const
36 {
37 return short_name != '\0';
38 }
39 bool has_long_name() const
40 {
41 return !long_name.empty();
42 }
43 friend std::ostream &operator<<(std::ostream &os, const Option &option)
44 {
45 if (option.has_long_name())
46 {
47 os << "--" << option.long_name;
48 }
49 else if (option.has_short_name())
50 {
51 os << "-" << option.short_name;
52 }
53 else
54 {
55 os << "--<unnamed>";
56 }
57 return os;
58 }
59 };
60
61 class Options final
62 {
63 friend class OptionsParser;
64
65 private:
66 std::vector<Option> options;
67 std::map<char, std::size_t> short_options;
68 std::map<std::string_view, std::size_t> long_options;
69
70 public:
71 Options(std::initializer_list<Option> options_)
72 : options(options_), short_options(), long_options()
73 {
74 for (std::size_t i = 0; i < options.size(); i++)
75 {
76 auto &option = options[i];
77 if (option.has_short_name())
78 short_options[option.short_name] = i;
79 if (option.has_long_name())
80 long_options[option.long_name] = i;
81 }
82 }
83
84 public:
85 std::vector<std::string_view> parse(const char *const *argv) const;
86 };
87
88 class OptionsParser final
89 {
90 private:
91 const Options &options;
92 const char *const *argv;
93 std::string_view argv0;
94 std::vector<bool> seen_options;
95 bool check_required = true;
96 std::optional<std::size_t> current_option_index;
97
98 private:
99 const Option &current_option()
100 {
101 static const Option default_option{};
102 if (current_option_index)
103 {
104 return options.options[*current_option_index];
105 }
106 else
107 {
108 return default_option;
109 }
110 }
111
112 public:
113 OptionsParser(const Options &options, const char *const *argv)
114 : options(options), argv(argv + 1),
115 argv0(argv[0] ? argv[0] : "<none>"),
116 seen_options(options.options.size(), false), current_option_index()
117 {
118 if (!argv[0])
119 {
120 static const char *null[] = {nullptr};
121 this->argv = null;
122 help_and_exit("missing program name");
123 }
124 }
125
126 private:
127 void parse_option(std::size_t option_index, std::string_view prefix,
128 std::string_view name,
129 std::optional<std::string_view> value)
130 {
131 auto &option = options.options[option_index];
132 switch (option.value_kind)
133 {
134 case OptionValueKind::None:
135 if (value)
136 {
137 help_and_exit("value not allowed for ", prefix, name);
138 }
139 break;
140 case OptionValueKind::Required:
141 if (!value)
142 {
143 if (*argv)
144 {
145 value = *argv++;
146 }
147 else
148 {
149 help_and_exit("missing value for ", prefix, name);
150 }
151 }
152 break;
153 }
154 seen_options[option_index] = true;
155 if (option.all_other_args_not_required)
156 check_required = false;
157 current_option_index = option_index;
158 option.parse_value(*this, value);
159 current_option_index = std::nullopt;
160 }
161
162 public:
163 std::vector<std::string_view> parse()
164 {
165 std::vector<std::string_view> retval;
166 constexpr auto npos = std::string_view::npos;
167 while (*argv)
168 {
169 std::string_view arg = *argv++;
170 static constexpr std::string_view long_prefix = "--"sv;
171 static constexpr std::string_view short_prefix = "-"sv;
172 if (arg.rfind(long_prefix, 0) == 0) // it starts with `--`
173 {
174 arg.remove_prefix(long_prefix.size());
175 auto eq = arg.find('=');
176 if (eq == 0)
177 {
178 eq = npos;
179 }
180 if (arg.empty()) // just `--`
181 {
182 while (*argv)
183 {
184 retval.push_back(*argv++);
185 }
186 break;
187 }
188 auto name = arg.substr(0, eq);
189 std::optional<std::string_view> value;
190 if (eq != npos)
191 {
192 value = arg.substr(eq + 1);
193 }
194 auto iter = options.long_options.find(name);
195 if (iter == options.long_options.end())
196 {
197 help_and_exit("unknown option: ", long_prefix, name);
198 }
199 auto option_index = iter->second;
200 parse_option(option_index, long_prefix, name, value);
201 continue;
202 }
203 else if (arg.rfind(short_prefix, 0) == 0) // it starts with `-`
204 {
205 arg.remove_prefix(short_prefix.size());
206 if (arg.empty()) // just `-`
207 {
208 retval.push_back(short_prefix);
209 continue;
210 }
211 while (!arg.empty())
212 {
213 auto name = arg.substr(0, 1);
214 arg.remove_prefix(1);
215 auto iter = options.short_options.find(name[0]);
216 if (iter == options.short_options.end())
217 {
218 help_and_exit("unknown option: ", short_prefix, name);
219 }
220 auto option_index = iter->second;
221 std::optional<std::string_view> value;
222 switch (options.options[option_index].value_kind)
223 {
224 case OptionValueKind::None:
225 break;
226 case OptionValueKind::Required:
227 auto eq = arg.rfind('=', 0);
228 if (eq != npos)
229 {
230 value = arg.substr(eq + 1);
231 }
232 else if (!arg.empty())
233 {
234 value = arg;
235 }
236 arg = "";
237 break;
238 }
239 parse_option(option_index, short_prefix, name, value);
240 }
241 continue;
242 }
243 else
244 {
245 retval.push_back(arg);
246 }
247 }
248 if (check_required)
249 {
250 for (std::size_t i = 0; i < options.options.size(); i++)
251 {
252 auto &option = options.options[i];
253 if (option.required && !seen_options[i])
254 {
255 help_and_exit("missing required option ", option);
256 }
257 }
258 }
259 return retval;
260 }
261 template <typename... Options>
262 [[noreturn]] void help_and_exit(const Options &...error_msg) const
263 {
264 auto &os = sizeof...(error_msg) == 0 ? std::cout : std::cerr;
265 if (sizeof...(error_msg) != 0)
266 {
267 ((os << "Error: ") << ... << error_msg) << std::endl;
268 }
269 os << "Usage: " << argv0;
270 for (auto &option : options.options)
271 {
272 os << " ";
273 if (!option.required)
274 {
275 os << "[";
276 }
277 auto sep = "";
278 if (option.has_short_name())
279 {
280 os << "-" << option.short_name;
281 sep = "|";
282 }
283 if (option.has_long_name())
284 {
285 os << sep << "--" << option.long_name;
286 }
287 switch (option.value_kind)
288 {
289 case OptionValueKind::None:
290 break;
291 // TODO: case OptionValueKind::Optional:
292 case OptionValueKind::Required:
293 os << " <value>";
294 break;
295 }
296 if (!option.required)
297 {
298 os << "]";
299 }
300 }
301 os << std::endl << "Options:" << std::endl;
302 for (auto &option : options.options)
303 {
304 auto sep = "";
305 if (option.has_short_name())
306 {
307 os << "-" << option.short_name;
308 sep = "|";
309 }
310 if (option.has_long_name())
311 {
312 os << sep << "--" << option.long_name;
313 }
314 os << " " << option.description << std::endl;
315 }
316 std::exit(sizeof...(error_msg) == 0 ? 0 : 1);
317 }
318 template <typename Int>
319 std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
320 std::optional<std::string_view> value, Int &i_value)
321 {
322 i_value = Int();
323 if (!value)
324 {
325 help_and_exit("missing value for ", current_option());
326 }
327 auto str = *value;
328 int base = 10;
329 if (0 == str.rfind("0x", 0) || 0 == str.rfind("0X", 0))
330 {
331 base = 16;
332 str.remove_prefix(2);
333 }
334 else if (0 == str.rfind("0o", 0) || 0 == str.rfind("0O", 0))
335 {
336 base = 8;
337 str.remove_prefix(2);
338 }
339 else if (0 == str.rfind("0b", 0) || 0 == str.rfind("0B", 0))
340 {
341 base = 2;
342 str.remove_prefix(2);
343 }
344 std::from_chars_result result = std::from_chars(
345 str.data(), str.data() + str.size(), i_value, base);
346 if (result.ptr != str.data() + str.size())
347 {
348 result.ec = std::errc::invalid_argument;
349 }
350 if (result.ec == std::errc::result_out_of_range)
351 {
352 help_and_exit("value out of range: ", current_option(), "=",
353 *value);
354 }
355 else if (result.ec != std::errc())
356 {
357 help_and_exit("invalid value for: ", current_option());
358 }
359 }
360 template <typename Int>
361 std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
362 std::optional<std::string_view> value, std::optional<Int> &i_value,
363 bool required = true)
364 {
365 if (!required && !value)
366 {
367 i_value = std::nullopt;
368 return;
369 }
370 i_value.emplace();
371 this->parse_int(value, i_value.value());
372 }
373 };
374
375 inline std::vector<std::string_view> Options::parse(
376 const char *const *argv) const
377 {
378 return OptionsParser(*this, argv).parse();
379 }
380
381 int main(int, char **argv)
382 {
383 Config config{};
384 Options options{
385 Option{
386 .short_name = 'h',
387 .long_name = "help",
388 .description = "Display usage and exit.",
389 .all_other_args_not_required = true,
390 .parse_value = [](OptionsParser &parser,
391 auto) { parser.help_and_exit(); },
392 },
393 Option{.short_name = 'j',
394 .long_name = "thread-count",
395 .description = "Number of threads to run on",
396 .value_kind = OptionValueKind::Required,
397 .parse_value =
398 [&](OptionsParser &parser, auto value) {
399 parser.parse_int(value, config.thread_count);
400 }},
401 Option{.short_name = 'n',
402 .long_name = "iter-count",
403 .description = "Number of iterations to run per thread",
404 .value_kind = OptionValueKind::Required,
405 .parse_value =
406 [&](OptionsParser &parser, auto value) {
407 parser.parse_int(value, config.iteration_count);
408 }},
409 };
410 OptionsParser parser(options, argv);
411 auto args = parser.parse();
412 if (!args.empty())
413 {
414 parser.help_and_exit("unexpected argument");
415 }
416 // TODO: invoke benchmarks
417 return 0;
418 }