Skip to content

Commit 9a05351

Browse files
committed
Refactor
1 parent 58a5e17 commit 9a05351

3 files changed

Lines changed: 265 additions & 247 deletions

File tree

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ find_package(Threads REQUIRED QUIET)
6767
######################################################################
6868

6969
# Source files
70-
set(SRC_FILES src/main.cpp src/pseudosquares_prime_sieve.cpp)
70+
set(SRC_FILES src/main.cpp
71+
src/CmdOptions.cpp
72+
src/pseudosquares_prime_sieve.cpp)
7173

7274
# Main executable
7375
add_executable(pseudosquares_prime_sieve ${SRC_FILES})

src/CmdOptions.cpp

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
///
2+
/// @file CmdOptions.cpp
3+
/// @brief Command-line option handling.
4+
///
5+
/// Copyright (C) 2025 Kim Walisch, <kim.walisch@gmail.com>
6+
///
7+
/// This file is distributed under the BSD License. See the COPYING
8+
/// file in the top level directory.
9+
///
10+
11+
#include "CmdOptions.hpp"
12+
#include "calculator.hpp"
13+
#include "int128_t.hpp"
14+
15+
#include <cstddef>
16+
#include <cctype>
17+
#include <map>
18+
#include <stdint.h>
19+
#include <stdexcept>
20+
#include <string>
21+
#include <utility>
22+
23+
void help(int exit_code);
24+
void version();
25+
26+
namespace {
27+
28+
enum OptionID
29+
{
30+
OPTION_DISTANCE,
31+
OPTION_HELP,
32+
OPTION_NUMBER,
33+
OPTION_PRINT,
34+
OPTION_THREADS,
35+
OPTION_VERSION
36+
};
37+
38+
/// Some command-line options require an additional parameter.
39+
/// Examples: --threads THREADS, -a ALPHA, ...
40+
enum IsParam
41+
{
42+
NO_PARAM,
43+
REQUIRED_PARAM,
44+
OPTIONAL_PARAM
45+
};
46+
47+
/// Options start with "-" or "--", then
48+
/// follows a Latin ASCII character.
49+
///
50+
bool isOption(const std::string& str)
51+
{
52+
// Option of type: -o...
53+
if (str.size() >= 2 &&
54+
str[0] == '-' &&
55+
((str[1] >= 'a' && str[1] <= 'z') ||
56+
(str[1] >= 'A' && str[1] <= 'Z')))
57+
return true;
58+
59+
// Option of type: --o...
60+
if (str.size() >= 3 &&
61+
str[0] == '-' &&
62+
str[1] == '-' &&
63+
((str[2] >= 'a' && str[2] <= 'z') ||
64+
(str[2] >= 'A' && str[2] <= 'Z')))
65+
return true;
66+
67+
return false;
68+
}
69+
70+
/// Parse the next command-line option.
71+
/// e.g. "--threads=32"
72+
/// -> opt.str = "--threads=32"
73+
/// -> opt.opt = "--threads"
74+
/// -> opt.val = "8"
75+
///
76+
template <typename T>
77+
Option parseOption(int argc,
78+
char* argv[],
79+
int& i,
80+
const T& optionMap)
81+
{
82+
Option opt;
83+
opt.str = argv[i];
84+
85+
if (opt.str.empty())
86+
throw std::runtime_error("unrecognized option ''");
87+
88+
// Check if the option has the format:
89+
// --opt or -o (but not --opt=N)
90+
if (optionMap.count(opt.str))
91+
{
92+
opt.opt = opt.str;
93+
IsParam isParam = optionMap.at(opt.str).second;
94+
95+
if (isParam == REQUIRED_PARAM)
96+
{
97+
i += 1;
98+
99+
if (i < argc)
100+
opt.val = argv[i];
101+
102+
// Prevent --threads --other-option
103+
if (opt.val.empty() || isOption(opt.val))
104+
throw std::runtime_error("missing value for option '" + opt.opt + "'");
105+
}
106+
107+
// If the option takes an optional argument we
108+
// assume the next value is an optional argument
109+
// if the value is not a vaild option.
110+
if (isParam == OPTIONAL_PARAM &&
111+
i + 1 < argc &&
112+
!std::string(argv[i + 1]).empty() &&
113+
!isOption(argv[i + 1]))
114+
{
115+
i += 1;
116+
opt.val = argv[i];
117+
}
118+
}
119+
else
120+
{
121+
// Here the option is either:
122+
// 1) An option of type: --opt[=N]
123+
// 2) An option of type: --opt[N]
124+
// 3) A number (e.g. the start number)
125+
126+
if (isOption(opt.str))
127+
{
128+
std::size_t pos = opt.str.find('=');
129+
130+
// Option of type: --opt=N
131+
if (pos != std::string::npos)
132+
{
133+
opt.opt = opt.str.substr(0, pos);
134+
opt.val = opt.str.substr(pos + 1);
135+
136+
// Print partial option: --opt (without =N)
137+
if (!optionMap.count(opt.opt))
138+
throw std::runtime_error("unrecognized option '" + opt.opt + "'");
139+
}
140+
else
141+
{
142+
// Option of type: --opt[N]
143+
pos = opt.str.find_first_of("0123456789");
144+
145+
if (pos == std::string::npos)
146+
opt.opt = opt.str;
147+
else
148+
{
149+
opt.opt = opt.str.substr(0, pos);
150+
opt.val = opt.str.substr(pos);
151+
}
152+
153+
// Print full option e.g.: --opt123
154+
if (!optionMap.count(opt.opt))
155+
throw std::runtime_error("unrecognized option '" + opt.str + "'");
156+
}
157+
158+
// Prevent '--option='
159+
if (opt.val.empty() &&
160+
optionMap.at(opt.opt).second == REQUIRED_PARAM)
161+
throw std::runtime_error("missing value for option '" + opt.opt + "'");
162+
}
163+
else
164+
{
165+
// Here the option is actually a number or
166+
// an integer arithmetic expression.
167+
opt.opt = "--number";
168+
opt.val = opt.str;
169+
170+
// This is not a valid number
171+
if (opt.str.find_first_of("0123456789") == std::string::npos)
172+
throw std::runtime_error("unrecognized option '" + opt.str + "'");
173+
174+
// Prevent negative numbers as there are
175+
// no negative prime numbers.
176+
if (opt.str.at(0) == '-')
177+
throw std::runtime_error("unrecognized option '" + opt.str + "'");
178+
}
179+
}
180+
181+
return opt;
182+
}
183+
184+
template <typename T>
185+
T getVal(const Option& opt)
186+
{
187+
try {
188+
return calculator::eval<T>(opt.val);
189+
}
190+
catch (std::exception& e) {
191+
throw std::runtime_error("invalid option '" + opt.opt + "=" + opt.val + "'\n" + e.what());
192+
}
193+
}
194+
195+
} // namespace
196+
197+
void CmdOptions::optionDistance(Option& opt)
198+
{
199+
uint128_t start = 0;
200+
uint128_t val = getVal<uint128_t>(opt);
201+
202+
if (!numbers.empty())
203+
start = numbers.front();
204+
205+
numbers.push_back(start + val);
206+
207+
if (opt.val.find_first_of("0123456789") != 0)
208+
numbers_str.push_back(to_string(numbers.back()));
209+
else
210+
{
211+
if (numbers_str.empty())
212+
numbers_str.push_back(opt.val);
213+
else
214+
numbers_str.push_back(numbers_str.back() + "+" + opt.val);
215+
}
216+
}
217+
218+
CmdOptions parseOptions(int argc, char** argv)
219+
{
220+
// No command-line options provided
221+
if (argc <= 1)
222+
help(/* exitCode */ 1);
223+
224+
/// Command-line options
225+
const std::map<std::string, std::pair<OptionID, IsParam>> optionMap =
226+
{
227+
{ "-d", std::make_pair(OPTION_DISTANCE, REQUIRED_PARAM) },
228+
{ "--dist", std::make_pair(OPTION_DISTANCE, REQUIRED_PARAM) },
229+
{ "-h", std::make_pair(OPTION_HELP, NO_PARAM) },
230+
{ "--help", std::make_pair(OPTION_HELP, NO_PARAM) },
231+
{ "--number", std::make_pair(OPTION_NUMBER, REQUIRED_PARAM) },
232+
{ "-p", std::make_pair(OPTION_PRINT, OPTIONAL_PARAM) },
233+
{ "--print", std::make_pair(OPTION_PRINT, OPTIONAL_PARAM) },
234+
{ "-t", std::make_pair(OPTION_THREADS, REQUIRED_PARAM) },
235+
{ "--threads", std::make_pair(OPTION_THREADS, REQUIRED_PARAM) },
236+
{ "-v", std::make_pair(OPTION_VERSION, NO_PARAM) },
237+
{ "--version", std::make_pair(OPTION_VERSION, NO_PARAM) }
238+
};
239+
240+
CmdOptions opts;
241+
242+
for (int i = 1; i < argc; i++)
243+
{
244+
Option opt = parseOption(argc, argv, i, optionMap);
245+
OptionID optionID = optionMap.at(opt.opt).first;
246+
247+
switch (optionID)
248+
{
249+
case OPTION_DISTANCE: opts.optionDistance(opt); break;
250+
case OPTION_NUMBER: opts.numbers.push_back(getVal<uint128_t>(opt));
251+
opts.numbers_str.push_back(opt.val); break;
252+
case OPTION_PRINT: opts.print_primes = true; break;
253+
case OPTION_THREADS: opts.threads = getVal<int>(opt); break;
254+
case OPTION_HELP: help(0); break;
255+
case OPTION_VERSION: version(); break;
256+
}
257+
}
258+
259+
return opts;
260+
}

0 commit comments

Comments
 (0)