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