ROSE 0.11.145.192
SuffixMultiplierParser.h
1#ifndef ROSE_CommandLine_SuffixMultiplierParser_H
2#define ROSE_CommandLine_SuffixMultiplierParser_H
3
4#include <Rose/As.h>
5#include <Rose/StringUtility/Escape.h>
6#include <rose_strtoull.h>
7
8#include <algorithm>
9#include <boost/numeric/conversion/cast.hpp>
10#include <cassert>
11#include <cerrno>
12#include <cmath>
13#include <cstdlib>
14#include <map>
15#include <Sawyer/CommandLine.h>
16#include <string>
17
18namespace Rose {
19namespace CommandLine {
20
34template<class T>
36public:
37 enum class Preferred { NO, YES };
38
39private:
40 struct Suffix {
41 T multiplier;
42 Preferred preferred;
43 };
44
45 using Suffixes = std::map<std::string /*name*/, Suffix>;
46 Suffixes suffixes_;
47 bool extendedSyntax_ = false;
48
49protected:
51
54
55public:
60
62 static Ptr instance() {
63 return Ptr(new SuffixMultiplierParser);
64 }
65
70
78 Ptr with(const std::string &suffix, T multiplier, Preferred preferred = Preferred::YES) {
79 suffixes_[suffix] = Suffix{.multiplier = multiplier, .preferred = preferred};
80 return as<SuffixMultiplierParser>(sharedFromThis());
81 }
82
83 Ptr with(const std::string &suffix, T multiplier, const std::string &alias1, const std::string &alias2 = "",
84 const std::string &alias3 = "", const std::string &alias4 = "") {
85 suffixes_[suffix] = Suffix{multiplier, Preferred::YES};
86 if (!alias1.empty())
87 suffixes_[alias1] = Suffix{multiplier, Preferred::NO};
88 if (!alias2.empty())
89 suffixes_[alias2] = Suffix{multiplier, Preferred::NO};
90 if (!alias3.empty())
91 suffixes_[alias3] = Suffix{multiplier, Preferred::NO};
92 if (!alias4.empty())
93 suffixes_[alias4] = Suffix{multiplier, Preferred::NO};
94 return as<SuffixMultiplierParser>(sharedFromThis());
95 }
114 bool extendedSyntax() const {
115 return extendedSyntax_;
116 }
118 extendedSyntax_ = b;
119 return as<SuffixMultiplierParser>(sharedFromThis());
120 }
124 T parse(const char *input, const char **rest) {
125 const char *s = input;
126 const char *r = nullptr;
127 T total = 0;
128
129 while (isspace(*s)) ++s;
130
131 while (*s) {
132 T n = parseNumber(s, &r, T{}); // throws if not possible
133 s = r;
134
135 while (*r && !isdigit(*r))
136 ++r;
137 if (s == r)
138 break;
139 std::string suffix(s, r);
140 auto found = suffixes_.find(suffix);
141 if (found == suffixes_.end()) {
142 total += n;
143 r = const_cast<char*>(s);
144 break;
145 } else {
146 total += n * found->second.multiplier;
147 s = r;
148 }
149 }
150
151 if (rest)
152 *rest = r;
153 return total;
154 }
155
157 T parse(const std::string &input) {
158 const char *s = input.c_str();
159 const char *rest = nullptr;
160 T retval = parse(s, &rest);
161 while (isspace(*rest)) ++rest;
162 if (*rest)
163 throw std::runtime_error("extra text after end of interval specification: \"" + StringUtility::cEscape(rest) + "\"");
164 return retval;
165 }
166
167 static std::pair<T, T> quotientRemainder(T product, T divisor, uint64_t) {
168 uint64_t q64 = product / divisor;
169 uint64_t r64 = product % divisor;
170 try {
171 T q = boost::numeric_cast<T>(q64);
172 T r = boost::numeric_cast<T>(r64);
173 return {q, r};
174 } catch (const boost::bad_numeric_cast&) {
175 throw std::runtime_error("integer overflow");
176 }
177 }
178
179 static std::pair<T, T> quotientRemainder(T product, T divisor, double) {
180 if (product < divisor) {
181 return {0, product};
182 } else {
183 double qd = std::floor(product / divisor);
184 double rd = std::fmod(product, divisor);
185 try {
186 T q = boost::numeric_cast<T>(qd);
187 T r = boost::numeric_cast<T>(rd);
188 return {q, r};
189 } catch (const boost::bad_numeric_cast&) {
190 throw std::runtime_error("floating-point overflow");
191 }
192 }
193 }
194
196 std::string toString(T value) {
197 // Get a list of preferred suffixes sorted by increasing multipliers. When two or more preferred suffixes have the same
198 // multiplier, use the one with the longest name. If name lengths tie, choose one arbitrarily. We do this by first
199 // sorting the suffixes according to increasing multiplier and decreasing name length; then removing duplicates based
200 // on multipliers only.
201 using Pair = std::pair<std::string, Suffix>;
202 std::vector<Pair> byValue(suffixes_.begin(), suffixes_.end());
203 byValue.erase(std::remove_if(byValue.begin(), byValue.end(),
204 [](const Pair &a) {
205 return a.second.preferred != Preferred::YES;
206 }),
207 byValue.end());
208 std::sort(byValue.begin(), byValue.end(),
209 [](const Pair &a, const Pair &b) {
210 if (a.second.multiplier != b.second.multiplier)
211 return a.second.multiplier < b.second.multiplier;
212 if (a.first.size() != b.first.size())
213 return a.first.size() > b.first.size();
214 return a.first < b.first;
215 });
216 byValue.erase(std::unique(byValue.begin(), byValue.end(),
217 [](const Pair &a, const Pair &b) {
218 return a.second.multiplier == b.second.multiplier;
219 }),
220 byValue.end());
221
222 // Form the output by trying successively smaller suffixes.
223 std::string retval;
224 while (value > 0) {
225 while (!byValue.empty() && byValue.back().second.multiplier > value)
226 byValue.pop_back();
227 if (byValue.empty())
228 break;
229
230 std::pair<T, T> qr = quotientRemainder(value, byValue.back().second.multiplier, T{});
231 assert(qr.first > 0);
232 retval += boost::lexical_cast<std::string>(qr.first) + byValue.back().first;
233 value = qr.second;
234 }
235
236 if (value > 0)
237 retval += boost::lexical_cast<std::string>(value);
238 if (retval.empty())
239 retval += "0";
240 return retval;
241 }
242
243private:
244 virtual Sawyer::CommandLine::ParsedValue operator()(const char *input, const char **rest,
245 const Sawyer::CommandLine::Location &loc) override {
246 T val = parse(input, rest);
247 std::string parsed(input, *rest - input);
248 return Sawyer::CommandLine::ParsedValue(val, loc, parsed, valueSaver());
249 }
250
251 // Parse integer literal w/out leading white space
252 T parseNumber(const char *input, const char **rest, uint64_t) {
253 assert(input);
254 assert(rest);
255 char *r = nullptr;
256 errno = 0;
257 uint64_t n = rose_strtoull(input, &r, extendedSyntax_ ? 0 : 10);
258 if (input == *rest)
259 throw std::runtime_error("unsigned integer expected");
260 if (ERANGE == errno)
261 throw std::runtime_error("integer magnitude is too large");
262 try {
263 T retval = boost::numeric_cast<T>(n);
264 *rest = r;
265 return retval;
266 } catch (const boost::bad_numeric_cast&) {
267 throw std::runtime_error("integer magnitude is too large");
268 }
269 }
270
271 // Parse floating point literal w/out leading white space
272 T parseNumber(const char *input, const char **rest, double) {
273 assert(input);
274 assert(rest);
275
276 // Find the end of the significand (digits followed by optional decimal point and more digits). We don't worry about
277 // how many digits there are on either side of the decimal point since strtod will do more strict checking later.
278 const char *significandEnd = input;
279 bool hadDecimalPoint;
280 while (isdigit(*significandEnd) || ('.' == *significandEnd && !hadDecimalPoint)) {
281 if ('.' == *significandEnd)
282 hadDecimalPoint = true;
283 ++significandEnd;
284 }
285
286 // For extended syntax, find the end of the optional exponent: [eE][-+]?\d+. Again, we don't have to be too precise
287 // because strtod will do the strict checking. However, we should be careful that the "e" is followed by at least
288 // one digit so as not to be confused by suffixes that start with "e" (other than the suffix "e" itself which will
289 // be ambiguous when followed immediately by a digit).
290 const char *end = significandEnd;
291 if (extendedSyntax_ && ('e' == *end || 'E' == *end)) {
292 ++end;
293 if ('+' == *end || '-' == *end)
294 ++end;
295 if (isdigit(*end)) {
296 while (isdigit(*end)) ++end;
297 } else {
298 end = significandEnd;
299 }
300 }
301
302 const std::string toParse(input, end);
303 const char *s = toParse.c_str();
304 char *r = nullptr;
305 errno = 0;
306 double d = strtod(s, &r);
307 if (s == r)
308 throw std::runtime_error("floating-point number expected");
309 if (ERANGE == errno)
310 throw std::runtime_error("floating-point value is out of range");
311 try {
312 T retval = boost::numeric_cast<T>(d);
313 *rest = input + (r - s);
314 return retval;
315 } catch (const boost::bad_numeric_cast&) {
316 throw std::runtime_error("floating-point value is out of range");
317 }
318 }
319};
320
321template<class T>
322typename SuffixMultiplierParser<T>::Ptr suffixMultiplierParser(T &storage) {
324}
325
326template<class T>
327typename SuffixMultiplierParser<T>::Ptr suffixMultiplierParser(std::vector<T> &storage) {
328 return SuffixMultiplierParser<T>::instance(Sawyer::CommandLine::TypedSaver<std::vector<T>>::instance(storage));
329}
330
331template<class T>
332typename SuffixMultiplierParser<T>::Ptr suffixMultiplierParser() {
334}
335
336} // namespace
337} // namespace
338
339#endif
Parse values followed by unit names.
Ptr with(const std::string &suffix, T multiplier, Preferred preferred=Preferred::YES)
Insert a suffix definition.
T parse(const char *input, const char **rest)
Parse from a C string.
static Ptr instance(const Sawyer::CommandLine::ValueSaver::Ptr &valueSaver)
Allocating constructor.
Ptr extendedSyntax(bool b)
Property: Allow extended syntax for numberic values.
Ptr with(const std::string &suffix, T multiplier, const std::string &alias1, const std::string &alias2="", const std::string &alias3="", const std::string &alias4="")
Insert a suffix definition.
Sawyer::SharedPointer< SuffixMultiplierParser > Ptr
Shared-ownership pointer to a Rose::CommandLine::SuffixMultiplierParser.
bool extendedSyntax() const
Property: Allow extended syntax for numberic values.
std::string toString(T value)
Unparse to a string.
T parse(const std::string &input)
Parse from a C++ string.
static Ptr instance()
Default allocating constructor.
Information about a parsed switch value.
Base class parsing a value from input.
const ValueSaver::Ptr valueSaver() const
Property: functor responsible for saving a parsed value in user storage.
SharedPointer< ValueParser > sharedFromThis()
Create a shared pointer from this.
ROSE_UTIL_API std::string cEscape(const std::string &, char context='"')
Escapes characters that are special to C/C++.
The ROSE library.
Position within a command-line.