Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Implementation of the double validator.
22 : *
23 : * This validator can be used to convert the value of a parameter to a
24 : * double with overflow and underflow verifications.
25 : */
26 :
27 : // self
28 : //
29 : #include "advgetopt/validator_double.h"
30 :
31 :
32 : //// advgetopt lib
33 : ////
34 : //#include "advgetopt/exception.h"
35 :
36 :
37 : // cppthread lib
38 : //
39 : #include <cppthread/log.h>
40 :
41 :
42 : // snapdev lib
43 : //
44 : #include <snapdev/not_used.h>
45 :
46 :
47 : // boost lib
48 : //
49 : #include <boost/algorithm/string/trim.hpp>
50 :
51 :
52 : // last include
53 : //
54 : #include <snapdev/poison.h>
55 :
56 :
57 :
58 :
59 : namespace advgetopt
60 : {
61 :
62 :
63 :
64 : namespace
65 : {
66 :
67 :
68 2 : class validator_double_factory
69 : : public validator_factory
70 : {
71 : public:
72 2 : validator_double_factory()
73 2 : {
74 2 : validator::register_validator(*this);
75 2 : }
76 :
77 4 : virtual std::string get_name() const override
78 : {
79 4 : return std::string("double");
80 : }
81 :
82 41 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
83 : {
84 41 : snapdev::NOT_USED(data); // ignore `data`
85 41 : return std::make_shared<validator_double>(data);
86 : }
87 : };
88 :
89 2 : validator_double_factory g_validator_double_factory;
90 :
91 :
92 : } // no name namespace
93 :
94 :
95 :
96 :
97 : /** \brief Initialize the double validator.
98 : *
99 : * The constructor accepts a string with values and ranges which are
100 : * used to limit the values that can be used with this parameter.
101 : *
102 : * Remember that the default does not have to be included in these
103 : * values. It will still be viewed as \em valid.
104 : *
105 : * The string uses the following format:
106 : *
107 : * \code
108 : * start: range
109 : * | start ',' range
110 : *
111 : * range: number
112 : * | number '...' number
113 : *
114 : * number: [-+]?[0-9]+(.[0-9]+([eE][+-][0-9]+)?)?
115 : * \endcode
116 : *
117 : * Note that a single number is considered to be a range and is managed
118 : * the exact same way. A value which matches any of the ranges is considered
119 : * valid.
120 : *
121 : * Example:
122 : *
123 : * \code
124 : * "-10.01...+10.05,0.0005661"
125 : * \endcode
126 : *
127 : * This example allows all values between -10.01 and +10.05 inclusive and also
128 : * allows the value 0.0005661.
129 : *
130 : * \param[in] ranges The ranges used to limit the double.
131 : */
132 41 : validator_double::validator_double(string_list_t const & range_list)
133 : {
134 41 : range_t range;
135 261 : for(auto r : range_list)
136 : {
137 220 : std::string::size_type const pos(r.find("..."));
138 220 : if(pos == std::string::npos)
139 : {
140 200 : if(!convert_string(r, range.f_minimum))
141 : {
142 0 : cppthread::log << cppthread::log_level_t::error
143 0 : << r
144 : << " is not a valid standalone value;"
145 : " it must only be valid floating points,"
146 0 : " optionally preceeded by a sign (+ or -)."
147 0 : << cppthread::end;
148 0 : continue;
149 : }
150 200 : range.f_maximum = range.f_minimum;
151 : }
152 : else
153 : {
154 40 : std::string min_value(r.substr(0, pos));
155 20 : boost::trim(min_value);
156 20 : if(!convert_string(min_value, range.f_minimum))
157 : {
158 0 : cppthread::log << cppthread::log_level_t::error
159 0 : << min_value
160 : << " is not a valid value for your range's start;"
161 : " it must only be valid floating points,"
162 0 : " optionally preceeded by a sign (+ or -)."
163 0 : << cppthread::end;
164 0 : continue;
165 : }
166 :
167 40 : std::string max_value(r.substr(pos + 3));
168 20 : boost::trim(max_value);
169 20 : if(!convert_string(max_value, range.f_maximum))
170 : {
171 0 : cppthread::log << cppthread::log_level_t::error
172 0 : << max_value
173 : << " is not a valid value for your range's end;"
174 : " it must only be valid floating points,"
175 0 : " optionally preceeded by a sign (+ or -)."
176 0 : << cppthread::end;
177 0 : continue;
178 : }
179 :
180 20 : if(range.f_minimum > range.f_maximum)
181 : {
182 0 : cppthread::log << cppthread::log_level_t::error
183 0 : << min_value
184 0 : << " has to be smaller or equal to "
185 0 : << max_value
186 0 : << "; you have an invalid range."
187 0 : << cppthread::end;
188 0 : continue;
189 : }
190 : }
191 220 : f_allowed_values.push_back(range);
192 : }
193 41 : }
194 :
195 :
196 : /** \brief Return the name of this validator.
197 : *
198 : * This function returns "double".
199 : *
200 : * \return "double".
201 : */
202 41 : std::string validator_double::name() const
203 : {
204 41 : return std::string("double");
205 : }
206 :
207 :
208 : /** \brief Determine whether value is a double.
209 : *
210 : * This function verifies that the specified value is a valid double.
211 : *
212 : * It makes sures that the value is only composed of digits (`[0-9]+`)
213 : * and optionally has a decimal pointer followed by more digits and
214 : * an optional exponent.
215 : *
216 : * The number may also start with a sign (`[-+]?`).
217 : *
218 : * If ranges were defined, then the function also verifies that the
219 : * value is within at least one of the ranges.
220 : *
221 : * \param[in] value The value to validate.
222 : *
223 : * \return true if the value validates.
224 : */
225 135456 : bool validator_double::validate(std::string const & value) const
226 : {
227 135456 : double result(0.0);
228 135456 : if(convert_string(value, result))
229 : {
230 51452 : if(f_allowed_values.empty())
231 : {
232 1514 : return true;
233 : }
234 :
235 272487 : for(auto f : f_allowed_values)
236 : {
237 232595 : if(result >= f.f_minimum
238 118250 : && result <= f.f_maximum)
239 : {
240 10046 : return true;
241 : }
242 : }
243 39892 : return false;
244 : }
245 :
246 84004 : return false;
247 : }
248 :
249 :
250 : /** \brief Convert a string to a double value.
251 : *
252 : * This function is used to convert a string to a double with full
253 : * boundary verification.
254 : *
255 : * \todo
256 : * This function calls std::strtod() which interprets the decimal separator
257 : * depending on the current locale. We really want to only accept periods.
258 : *
259 : * \warning
260 : * There is no range checks in this function since it does not have access
261 : * to the ranges (i.e. it is static).
262 : *
263 : * \param[in] value The value to be converted to a double.
264 : * \param[out] result The resulting double.
265 : *
266 : * \return true if the conversion succeeded.
267 : */
268 1888442 : bool validator_double::convert_string(std::string const & value, double & result)
269 : {
270 1888442 : char const * start(value.c_str());
271 :
272 : // do not allow spaces before the number
273 : //
274 1888442 : if(start[0] != '+'
275 1529465 : && start[0] != '-'
276 785364 : && (start[0] < '0' || start[0] > '9'))
277 : {
278 42005 : return false;
279 : }
280 :
281 1846437 : char * end(nullptr);
282 1846437 : errno = 0;
283 1846437 : result = std::strtod(start, &end);
284 :
285 : // do not allow anything after the last digit
286 : // also return false on an overflow
287 : //
288 1846437 : return end == start + value.length()
289 1846437 : && errno != ERANGE;
290 : }
291 :
292 :
293 :
294 6 : } // namespace advgetopt
295 : // vim: ts=4 sw=4 et
|