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 : // cppthread
33 : //
34 : #include <cppthread/log.h>
35 :
36 :
37 : // snapdev
38 : //
39 : #include <snapdev/not_used.h>
40 :
41 :
42 : // boost
43 : //
44 : #include <boost/algorithm/string/trim.hpp>
45 :
46 :
47 : // last include
48 : //
49 : #include <snapdev/poison.h>
50 :
51 :
52 :
53 :
54 : namespace advgetopt
55 : {
56 :
57 :
58 :
59 : namespace
60 : {
61 :
62 :
63 2 : class validator_double_factory
64 : : public validator_factory
65 : {
66 : public:
67 2 : validator_double_factory()
68 2 : {
69 2 : validator::register_validator(*this);
70 2 : }
71 :
72 4 : virtual std::string get_name() const override
73 : {
74 4 : return std::string("double");
75 : }
76 :
77 42 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
78 : {
79 42 : snapdev::NOT_USED(data); // ignore `data`
80 42 : return std::make_shared<validator_double>(data);
81 : }
82 : };
83 :
84 2 : validator_double_factory g_validator_double_factory;
85 :
86 :
87 : } // no name namespace
88 :
89 :
90 :
91 :
92 : /** \brief Initialize the double validator.
93 : *
94 : * The constructor accepts a string with values and ranges which are
95 : * used to limit the values that can be used with this parameter.
96 : *
97 : * Remember that the default does not have to be included in these
98 : * values. It will still be viewed as \em valid.
99 : *
100 : * The string uses the following format:
101 : *
102 : * \code
103 : * start: range
104 : * | start ',' range
105 : *
106 : * range: number
107 : * | number '...' number
108 : *
109 : * number: [-+]?[0-9]+(.[0-9]+([eE][+-][0-9]+)?)?
110 : * \endcode
111 : *
112 : * Note that a single number is considered to be a range and is managed
113 : * the exact same way. A value which matches any of the ranges is considered
114 : * valid.
115 : *
116 : * Example:
117 : *
118 : * \code
119 : * "-10.01...+10.05,0.0005661"
120 : * \endcode
121 : *
122 : * This example allows all values between -10.01 and +10.05 inclusive and also
123 : * allows the value 0.0005661.
124 : *
125 : * \param[in] ranges The ranges used to limit the double.
126 : */
127 42 : validator_double::validator_double(string_list_t const & range_list)
128 : {
129 42 : range_t range;
130 252 : for(auto r : range_list)
131 : {
132 214 : std::string::size_type const pos(r.find("..."));
133 214 : if(pos == std::string::npos)
134 : {
135 192 : if(!convert_string(r, range.f_minimum))
136 : {
137 2 : cppthread::log << cppthread::log_level_t::error
138 1 : << r
139 : << " is not a valid standalone value;"
140 : " it must be a valid floating point,"
141 1 : " optionally preceeded by a sign (+ or -)."
142 2 : << cppthread::end;
143 1 : continue;
144 : }
145 190 : range.f_maximum = range.f_minimum;
146 : }
147 : else
148 : {
149 43 : std::string min_value(r.substr(0, pos));
150 23 : boost::trim(min_value);
151 24 : if(!convert_string(min_value, range.f_minimum))
152 : {
153 2 : cppthread::log << cppthread::log_level_t::error
154 1 : << min_value
155 : << " is not a valid value for your range's start;"
156 : " it must be a valid floating point,"
157 1 : " optionally preceeded by a sign (+ or -)."
158 2 : << cppthread::end;
159 1 : continue;
160 : }
161 :
162 42 : std::string max_value(r.substr(pos + 3));
163 22 : boost::trim(max_value);
164 23 : if(!convert_string(max_value, range.f_maximum))
165 : {
166 2 : cppthread::log << cppthread::log_level_t::error
167 1 : << max_value
168 : << " is not a valid value for your range's end;"
169 : " it must be a valid floating point,"
170 1 : " optionally preceeded by a sign (+ or -)."
171 2 : << cppthread::end;
172 1 : continue;
173 : }
174 :
175 22 : if(range.f_minimum > range.f_maximum)
176 : {
177 2 : cppthread::log << cppthread::log_level_t::error
178 1 : << min_value
179 1 : << " has to be smaller or equal to "
180 1 : << max_value
181 1 : << "; you have an invalid range."
182 2 : << cppthread::end;
183 1 : continue;
184 : }
185 : }
186 210 : f_allowed_values.push_back(range);
187 : }
188 42 : }
189 :
190 :
191 : /** \brief Return the name of this validator.
192 : *
193 : * This function returns "double".
194 : *
195 : * \return "double".
196 : */
197 41 : std::string validator_double::name() const
198 : {
199 41 : return std::string("double");
200 : }
201 :
202 :
203 : /** \brief Determine whether value is a double.
204 : *
205 : * This function verifies that the specified value is a valid double.
206 : *
207 : * It makes sures that the value is only composed of digits (`[0-9]+`)
208 : * and optionally has a decimal pointer followed by more digits and
209 : * an optional exponent.
210 : *
211 : * The number may also start with a sign (`[-+]?`).
212 : *
213 : * If ranges were defined, then the function also verifies that the
214 : * value is within at least one of the ranges.
215 : *
216 : * \param[in] value The value to validate.
217 : *
218 : * \return true if the value validates.
219 : */
220 135535 : bool validator_double::validate(std::string const & value) const
221 : {
222 135535 : double result(0.0);
223 135535 : if(convert_string(value, result))
224 : {
225 51531 : if(f_allowed_values.empty())
226 : {
227 1522 : return true;
228 : }
229 :
230 263643 : for(auto f : f_allowed_values)
231 : {
232 222330 : if(result >= f.f_minimum
233 111644 : && result <= f.f_maximum)
234 : {
235 8696 : return true;
236 : }
237 : }
238 41313 : return false;
239 : }
240 :
241 84004 : return false;
242 : }
243 :
244 :
245 : /** \brief Convert a string to a double value.
246 : *
247 : * This function is used to convert a string to a double with full
248 : * boundary verification.
249 : *
250 : * \todo
251 : * This function calls std::strtod() which interprets the decimal separator
252 : * depending on the current locale. We really want to only accept periods.
253 : *
254 : * \warning
255 : * There is no range checks in this function since it does not have access
256 : * to the ranges (i.e. it is static).
257 : *
258 : * \param[in] value The value to be converted to a double.
259 : * \param[out] result The resulting double.
260 : *
261 : * \return true if the conversion succeeded.
262 : */
263 1890789 : bool validator_double::convert_string(std::string const & value, double & result)
264 : {
265 1890789 : char const * start(value.c_str());
266 :
267 : // do not allow spaces before the number
268 : //
269 1890789 : if(start[0] != '+'
270 1529492 : && start[0] != '-'
271 790252 : && (start[0] < '0' || start[0] > '9'))
272 : {
273 42008 : return false;
274 : }
275 :
276 1848781 : char * end(nullptr);
277 1848781 : errno = 0;
278 1848781 : result = std::strtod(start, &end);
279 :
280 : // do not allow anything after the last digit
281 : // also return false on an overflow
282 : //
283 1848781 : return end == start + value.length()
284 1848781 : && errno != ERANGE;
285 : }
286 :
287 :
288 :
289 6 : } // namespace advgetopt
290 : // vim: ts=4 sw=4 et
|