1 ///
2 /// @file cmdoptions.cpp
3 /// @brief Parse command-line options for the primesieve console
4 /// (terminal) application.
5 ///
6 /// Copyright (C) 2021 Kim Walisch, <kim.walisch@gmail.com>
7 ///
8 /// This file is distributed under the BSD License. See the COPYING
9 /// file in the top level directory.
10 ///
11
12 #include "cmdoptions.hpp"
13
14 #include <primesieve/calculator.hpp>
15 #include <primesieve/CpuInfo.hpp>
16 #include <primesieve/PrimeSieve.hpp>
17 #include <primesieve/primesieve_error.hpp>
18
19 #include <cstddef>
20 #include <cstdlib>
21 #include <iostream>
22 #include <map>
23 #include <stdint.h>
24 #include <string>
25 #include <utility>
26
27 void help(int exitCode);
28 void test();
29 void version();
30
31 using namespace std;
32 using namespace primesieve;
33
34 namespace {
35
36 enum OptionID
37 {
38 OPTION_COUNT,
39 OPTION_CPU_INFO,
40 OPTION_HELP,
41 OPTION_NTH_PRIME,
42 OPTION_NO_STATUS,
43 OPTION_NUMBER,
44 OPTION_DISTANCE,
45 OPTION_PRINT,
46 OPTION_QUIET,
47 OPTION_SIZE,
48 OPTION_TEST,
49 OPTION_THREADS,
50 OPTION_TIME,
51 OPTION_VERSION
52 };
53
54 /// Some command-line options require an additional parameter.
55 /// Examples: --threads THREADS, -a ALPHA, ...
56 enum IsParam
57 {
58 NO_PARAM,
59 REQUIRED_PARAM,
60 OPTIONAL_PARAM
61 };
62
63 /// Command-line options
64 map<string, std::pair<OptionID, IsParam>> optionMap =
65 {
66 { "-c", make_pair(OPTION_COUNT, OPTIONAL_PARAM) },
67 { "--count", make_pair(OPTION_COUNT, OPTIONAL_PARAM) },
68 { "--cpu-info", make_pair(OPTION_CPU_INFO, NO_PARAM) },
69 { "-h", make_pair(OPTION_HELP, NO_PARAM) },
70 { "--help", make_pair(OPTION_HELP, NO_PARAM) },
71 { "-n", make_pair(OPTION_NTH_PRIME, NO_PARAM) },
72 { "--nthprime", make_pair(OPTION_NTH_PRIME, NO_PARAM) },
73 { "--nth-prime", make_pair(OPTION_NTH_PRIME, NO_PARAM) },
74 { "--no-status", make_pair(OPTION_NO_STATUS, NO_PARAM) },
75 { "--number", make_pair(OPTION_NUMBER, REQUIRED_PARAM) },
76 { "-d", make_pair(OPTION_DISTANCE, REQUIRED_PARAM) },
77 { "--dist", make_pair(OPTION_DISTANCE, REQUIRED_PARAM) },
78 { "-p", make_pair(OPTION_PRINT, OPTIONAL_PARAM) },
79 { "--print", make_pair(OPTION_PRINT, OPTIONAL_PARAM) },
80 { "-q", make_pair(OPTION_QUIET, NO_PARAM) },
81 { "--quiet", make_pair(OPTION_QUIET, NO_PARAM) },
82 { "-s", make_pair(OPTION_SIZE, REQUIRED_PARAM) },
83 { "--size", make_pair(OPTION_SIZE, REQUIRED_PARAM) },
84 { "--test", make_pair(OPTION_TEST, NO_PARAM) },
85 { "-t", make_pair(OPTION_THREADS, REQUIRED_PARAM) },
86 { "--threads", make_pair(OPTION_THREADS, REQUIRED_PARAM) },
87 { "--time", make_pair(OPTION_TIME, NO_PARAM) },
88 { "-v", make_pair(OPTION_VERSION, NO_PARAM) },
89 { "--version", make_pair(OPTION_VERSION, NO_PARAM) }
90 };
91
92 /// Command-line option
93 struct Option
94 {
95 // Example:
96 // str = "--threads=32"
97 // opt = "--threads"
98 // val = "32"
99 string str;
100 string opt;
101 string val;
102
103 template <typename T>
getValue__anon29e207570111::Option104 T getValue() const
105 {
106 try {
107 return calculator::eval<T>(val);;
108 }
109 catch (std::exception&) {
110 throw primesieve_error("invalid option '" + opt + "=" + val + "'");
111 }
112 }
113 };
114
115 /// Options start with "-" or "--", then
116 /// follows a Latin ASCII character.
117 ///
isOption(const string & str)118 bool isOption(const string& str)
119 {
120 // Option of type: -o...
121 if (str.size() >= 2 &&
122 str[0] == '-' &&
123 ((str[1] >= 'a' && str[1] <= 'z') ||
124 (str[1] >= 'A' && str[1] <= 'Z')))
125 return true;
126
127 // Option of type: --o...
128 if (str.size() >= 3 &&
129 str[0] == '-' &&
130 str[1] == '-' &&
131 ((str[2] >= 'a' && str[2] <= 'z') ||
132 (str[2] >= 'A' && str[2] <= 'Z')))
133 return true;
134
135 return false;
136 }
137
138 /// Parse the next command-line option.
139 /// e.g. "--threads=32"
140 /// -> opt.str = "--threads=32"
141 /// -> opt.opt = "--threads"
142 /// -> opt.val = "8"
143 ///
parseOption(int argc,char * argv[],int & i)144 Option parseOption(int argc, char* argv[], int& i)
145 {
146 Option opt;
147 opt.str = argv[i];
148
149 if (opt.str.empty())
150 throw primesieve_error("unrecognized option ''");
151
152 // Check if the option has the format:
153 // --opt or -o (but not --opt=N)
154 if (optionMap.count(opt.str))
155 {
156 opt.opt = opt.str;
157 IsParam isParam = optionMap[opt.str].second;
158
159 if (isParam == REQUIRED_PARAM)
160 {
161 i += 1;
162
163 if (i < argc)
164 opt.val = argv[i];
165
166 // Prevent --threads --other-option
167 if (opt.val.empty() || isOption(opt.val))
168 throw primesieve_error("missing value for option '" + opt.opt + "'");
169 }
170
171 // If the option takes an optional argument we
172 // assume the next value is an optional argument
173 // if the value is not a vaild option.
174 if (isParam == OPTIONAL_PARAM &&
175 i + 1 < argc &&
176 !string(argv[i + 1]).empty() &&
177 !isOption(argv[i + 1]))
178 {
179 i += 1;
180 opt.val = argv[i];
181 }
182 }
183 else
184 {
185 // Here the option is either:
186 // 1) An option of type: --opt[=N]
187 // 2) An option of type: --opt[N]
188 // 3) A number (e.g. the start number)
189
190 if (isOption(opt.str))
191 {
192 size_t pos = opt.str.find("=");
193
194 // Option of type: --opt=N
195 if (pos != string::npos)
196 {
197 opt.opt = opt.str.substr(0, pos);
198 opt.val = opt.str.substr(pos + 1);
199
200 // Print partial option: --opt (without =N)
201 if (!optionMap.count(opt.opt))
202 throw primesieve_error("unrecognized option '" + opt.opt + "'");
203 }
204 else
205 {
206 // Option of type: --opt[N]
207 pos = opt.str.find_first_of("0123456789");
208
209 if (pos == string::npos)
210 opt.opt = opt.str;
211 else
212 {
213 opt.opt = opt.str.substr(0, pos);
214 opt.val = opt.str.substr(pos);
215 }
216
217 // Print full option e.g.: --opt123
218 if (!optionMap.count(opt.opt))
219 throw primesieve_error("unrecognized option '" + opt.str + "'");
220 }
221
222 // Prevent '--option='
223 if (opt.val.empty() &&
224 optionMap[opt.opt].second == REQUIRED_PARAM)
225 throw primesieve_error("missing value for option '" + opt.opt + "'");
226 }
227 else
228 {
229 // Here the option is actually a number or
230 // an integer arithmetic expression.
231 opt.opt = "--number";
232 opt.val = opt.str;
233
234 // This is not a valid number
235 if (opt.str.find_first_of("0123456789") == string::npos)
236 throw primesieve_error("unrecognized option '" + opt.str + "'");
237
238 // Prevent negative numbers as there are
239 // no negative prime numbers.
240 if (opt.str.at(0) == '-')
241 throw primesieve_error("unrecognized option '" + opt.str + "'");
242 }
243 }
244
245 return opt;
246 }
247
optionPrint(Option & opt,CmdOptions & opts)248 void optionPrint(Option& opt,
249 CmdOptions& opts)
250 {
251 opts.quiet = true;
252
253 // by default print primes
254 if (opt.val.empty())
255 opt.val = "1";
256
257 switch (opt.getValue<int>())
258 {
259 case 1: opts.flags |= PRINT_PRIMES; break;
260 case 2: opts.flags |= PRINT_TWINS; break;
261 case 3: opts.flags |= PRINT_TRIPLETS; break;
262 case 4: opts.flags |= PRINT_QUADRUPLETS; break;
263 case 5: opts.flags |= PRINT_QUINTUPLETS; break;
264 case 6: opts.flags |= PRINT_SEXTUPLETS; break;
265 default: throw primesieve_error("invalid option '" + opt.str + "'");
266 }
267 }
268
optionCount(Option & opt,CmdOptions & opts)269 void optionCount(Option& opt,
270 CmdOptions& opts)
271 {
272 // by default count primes
273 if (opt.val.empty())
274 opt.val = "1";
275
276 int n = opt.getValue<int>();
277
278 for (; n > 0; n /= 10)
279 {
280 switch (n % 10)
281 {
282 case 1: opts.flags |= COUNT_PRIMES; break;
283 case 2: opts.flags |= COUNT_TWINS; break;
284 case 3: opts.flags |= COUNT_TRIPLETS; break;
285 case 4: opts.flags |= COUNT_QUADRUPLETS; break;
286 case 5: opts.flags |= COUNT_QUINTUPLETS; break;
287 case 6: opts.flags |= COUNT_SEXTUPLETS; break;
288 default: throw primesieve_error("invalid option '" + opt.str + "'");
289 }
290 }
291 }
292
optionDistance(Option & opt,CmdOptions & opts)293 void optionDistance(Option& opt,
294 CmdOptions& opts)
295 {
296 uint64_t start = 0;
297 uint64_t val = opt.getValue<uint64_t>();
298 auto& numbers = opts.numbers;
299
300 if (!numbers.empty())
301 start = numbers[0];
302
303 numbers.push_back(start + val);
304 }
305
optionCpuInfo()306 void optionCpuInfo()
307 {
308 const CpuInfo cpu;
309
310 if (cpu.hasCpuName())
311 cout << cpu.cpuName() << endl;
312 else
313 cout << "CPU name: unknown" << endl;
314
315 if (cpu.hasLogicalCpuCores())
316 cout << "Logical CPU cores: " << cpu.logicalCpuCores() << endl;
317 else
318 cout << "Logical CPU cores: unknown" << endl;
319
320 if (cpu.hasL1Cache())
321 cout << "L1 cache size: " << (cpu.l1CacheBytes() >> 10) << " KiB" << endl;
322
323 if (cpu.hasL2Cache())
324 cout << "L2 cache size: " << (cpu.l2CacheBytes() >> 10) << " KiB" << endl;
325
326 if (cpu.hasL3Cache())
327 cout << "L3 cache size: " << (cpu.l3CacheBytes() >> 20) << " MiB" << endl;
328
329 if (cpu.hasL1Cache())
330 {
331 if (!cpu.hasL1Sharing())
332 cout << "L1 cache sharing: unknown" << endl;
333 else
334 cout << "L1 cache sharing: " << cpu.l1Sharing()
335 << ((cpu.l1Sharing() > 1) ? " threads" : " thread") << endl;
336 }
337
338 if (cpu.hasL2Cache())
339 {
340 if (!cpu.hasL2Sharing())
341 cout << "L2 cache sharing: unknown" << endl;
342 else
343 cout << "L2 cache sharing: " << cpu.l2Sharing()
344 << ((cpu.l2Sharing() > 1) ? " threads" : " thread") << endl;
345 }
346
347 if (cpu.hasL3Cache())
348 {
349 if (!cpu.hasL3Sharing())
350 cout << "L3 cache sharing: unknown" << endl;
351 else
352 cout << "L3 cache sharing: " << cpu.l3Sharing()
353 << ((cpu.l3Sharing() > 1) ? " threads" : " thread") << endl;
354 }
355
356 if (!cpu.hasL1Cache() &&
357 !cpu.hasL2Cache() &&
358 !cpu.hasL3Cache())
359 {
360 cout << "L1 cache size: unknown" << endl;
361 cout << "L2 cache size: unknown" << endl;
362 cout << "L3 cache size: unknown" << endl;
363 cout << "L1 cache sharing: unknown" << endl;
364 cout << "L2 cache sharing: unknown" << endl;
365 cout << "L3 cache sharing: unknown" << endl;
366 }
367
368 exit(0);
369 }
370
371 } // namespace
372
parseOptions(int argc,char * argv[])373 CmdOptions parseOptions(int argc, char* argv[])
374 {
375 CmdOptions opts;
376
377 // No command-line options provided
378 if (argc <= 1)
379 help(/* exitCode */ 1);
380
381 for (int i = 1; i < argc; i++)
382 {
383 Option opt = parseOption(argc, argv, i);
384 OptionID optionID = optionMap[opt.opt].first;
385
386 switch (optionID)
387 {
388 case OPTION_COUNT: optionCount(opt, opts); break;
389 case OPTION_CPU_INFO: optionCpuInfo(); break;
390 case OPTION_DISTANCE: optionDistance(opt, opts); break;
391 case OPTION_PRINT: optionPrint(opt, opts); break;
392 case OPTION_SIZE: opts.sieveSize = opt.getValue<int>(); break;
393 case OPTION_THREADS: opts.threads = opt.getValue<int>(); break;
394 case OPTION_QUIET: opts.quiet = true; break;
395 case OPTION_NTH_PRIME: opts.nthPrime = true; break;
396 case OPTION_NO_STATUS: opts.status = false; break;
397 case OPTION_TIME: opts.time = true; break;
398 case OPTION_NUMBER: opts.numbers.push_back(opt.getValue<uint64_t>()); break;
399 case OPTION_HELP: help(/* exitCode */ 0); break;
400 case OPTION_TEST: test(); break;
401 case OPTION_VERSION: version(); break;
402 }
403 }
404
405 if (opts.numbers.empty())
406 throw primesieve_error("missing STOP number");
407
408 if (opts.quiet)
409 opts.status = false;
410 else
411 opts.time = true;
412
413 return opts;
414 }
415