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