1 | // **************************************************************************
|
---|
2 | /** @class Converter
|
---|
3 |
|
---|
4 | @brief An interpreter to convert a command line into a command plus data memory
|
---|
5 |
|
---|
6 | The Converter class interprets arguments in a string accoring to the
|
---|
7 | given format definition and produces a corresponding memory block from it
|
---|
8 | which can be attached to an event later.
|
---|
9 |
|
---|
10 | The format is given according to the Dim format description:
|
---|
11 |
|
---|
12 | The format parameter specifies the contents of the structure in the
|
---|
13 | form T:N[;T:N]*[;T] where T is the item type: (I)nteger, (C)haracter,
|
---|
14 | (L)ong, (S)hort, (F)loat, (D)ouble, X(tra long==long long) and N is the
|
---|
15 | number of such items. The type alone at the end means all following items
|
---|
16 | are of the same type. Example: "I:3;F:2;C" means 3 Integers, 2 Floats and
|
---|
17 | Characters until the end. The format parameter is used for
|
---|
18 | communicating between different platforms.
|
---|
19 |
|
---|
20 | For example:
|
---|
21 |
|
---|
22 | \code
|
---|
23 | Converter c(cout, "I:1;F:2;I:2", "COMMAND 1 2.5 4.2 3 4");
|
---|
24 | \endcode
|
---|
25 |
|
---|
26 | would produce a 20 byte data block with the integers 1, the floats
|
---|
27 | 2.5 and 4.2, and the intergers 3 and 4, in this order.
|
---|
28 |
|
---|
29 | The opposite direction is also possible
|
---|
30 |
|
---|
31 | \code
|
---|
32 | Converter c(cout, "I:1;F:2;I:2", pointer, size);
|
---|
33 | \endcode
|
---|
34 |
|
---|
35 | In addition to converting the options from a string into binary format
|
---|
36 | or back all found values are also put into a vector of boost::any objects.
|
---|
37 | They can be accessed using e.g.
|
---|
38 |
|
---|
39 | \code
|
---|
40 | Converter c(cout, "I:1;F:1;B:1;A:1;C", "112 5.5 on this is a test");
|
---|
41 |
|
---|
42 | cout << c.Get<int>(0) << endl; // prints '112'
|
---|
43 | cout << c.Get<float>(1) << endl; // prints '5.5'
|
---|
44 | cout << c.Get<bool>(2) << endl; // prints '1'
|
---|
45 | cout << c.Get<string>(3) << endl; // prints 'this'
|
---|
46 | cout << c.Get<string>(4) << endl; // prints 'is a test'
|
---|
47 | \endcode
|
---|
48 |
|
---|
49 | The format parameter \b W(ord) is dedicated to this kind of conversion and
|
---|
50 | not understood by Dim. In addition there are \b O(ptions) which are like
|
---|
51 | Words but can be omitted. They should only be used at the end of the string.
|
---|
52 | \b B(ool) is also special. It evaluates true/false, yes/no, on/off, 1/0.
|
---|
53 |
|
---|
54 | Access with Converter::Get() is exception safe. Access with Converter::At()
|
---|
55 | would throw an exception if the index is out of bounds or the conversion
|
---|
56 | fails. (This is the prefered method if the type is well known, to easily
|
---|
57 | detect programing faults)
|
---|
58 |
|
---|
59 | @remark
|
---|
60 | Most probably we support more formats than dim does...
|
---|
61 |
|
---|
62 | */
|
---|
63 | // **************************************************************************
|
---|
64 | #include "Converter.h"
|
---|
65 |
|
---|
66 | #include <iomanip>
|
---|
67 | #include <sstream>
|
---|
68 |
|
---|
69 | #include <cctype> // std::tolower
|
---|
70 | #include <algorithm> // std::transform
|
---|
71 |
|
---|
72 | #include <boost/regex.hpp>
|
---|
73 |
|
---|
74 | #include "Readline.h"
|
---|
75 | #include "WindowLog.h"
|
---|
76 |
|
---|
77 | using namespace std;
|
---|
78 |
|
---|
79 | template <class T>
|
---|
80 | void Converter::EvalImp(int i, std::stringstream &line, std::vector<char> &v, const T &val)
|
---|
81 | {
|
---|
82 | if (!line)
|
---|
83 | wout << " arg[" << i << "]";
|
---|
84 | else
|
---|
85 | wout << " (" << val << ")";
|
---|
86 |
|
---|
87 | vec.push_back(val);
|
---|
88 |
|
---|
89 | v.insert(v.end(),
|
---|
90 | reinterpret_cast<const char*>(&val),
|
---|
91 | reinterpret_cast<const char*>(&val+1));
|
---|
92 | }
|
---|
93 |
|
---|
94 |
|
---|
95 | // --------------------------------------------------------------------------
|
---|
96 | //
|
---|
97 | //! Gets a value of the template type T from the stringstream and adds the
|
---|
98 | //! value as binary data to the end of the vector. If a value couldn't be
|
---|
99 | //! obtained it is set to 0.
|
---|
100 | //!
|
---|
101 | //! Adds the value to Converter::vec.
|
---|
102 | //!
|
---|
103 | //! @param i
|
---|
104 | //! The number of the processed argument (currently used for some debug
|
---|
105 | //! output when an argument is not available and artificially set to 0)
|
---|
106 | //!
|
---|
107 | //! @param line
|
---|
108 | //! The stringstream from which the data should be read
|
---|
109 | //!
|
---|
110 | //! @param vec
|
---|
111 | //! The vector of bytes at which end the data is added in binary format
|
---|
112 | //!
|
---|
113 | template <class T>
|
---|
114 | void Converter::Eval(int i, std::stringstream &line, std::vector<char> &v)
|
---|
115 | {
|
---|
116 | T val;
|
---|
117 | line >> val;
|
---|
118 |
|
---|
119 | EvalImp(i, line, v, val);
|
---|
120 | }
|
---|
121 |
|
---|
122 | // --------------------------------------------------------------------------
|
---|
123 | //
|
---|
124 | //! This functions works similar the the template Eval but is dedicated to
|
---|
125 | //! bools. It evaluates yes/no, on/off, true/false and 1/0.
|
---|
126 | //!
|
---|
127 | //! Sets the failbit of the stream if the "value" is not known.
|
---|
128 | //!
|
---|
129 | //! Adds the value to Converter::vec.
|
---|
130 | //!
|
---|
131 | //! @param line
|
---|
132 | //! The stringstream from which the data should be read
|
---|
133 | //!
|
---|
134 | //! @param vec
|
---|
135 | //! The vector of bytes at which end the data is added in binary format
|
---|
136 | //!
|
---|
137 | //! @returns
|
---|
138 | //! The bool evaluated
|
---|
139 | //!
|
---|
140 | void Converter::EvalBool(int i, std::stringstream &line, std::vector<char> &v)
|
---|
141 | {
|
---|
142 | string buf;
|
---|
143 | line >> buf;
|
---|
144 | transform(buf.begin(), buf.end(), buf.begin(), (int(*)(int)) std::tolower);
|
---|
145 |
|
---|
146 | if (buf=="yes" || buf=="true" || buf=="on" || buf=="1")
|
---|
147 | {
|
---|
148 | EvalImp(i, line, v, bool(true));
|
---|
149 | return;
|
---|
150 | }
|
---|
151 |
|
---|
152 | if (buf=="no" || buf=="false" || buf=="off" || buf=="0")
|
---|
153 | {
|
---|
154 | EvalImp(i, line, v, bool(false));
|
---|
155 | return;
|
---|
156 | }
|
---|
157 |
|
---|
158 | line.clear(ios::failbit);
|
---|
159 | }
|
---|
160 |
|
---|
161 | void Converter::EvalString(int i, std::stringstream &line, std::vector<char> &v)
|
---|
162 | {
|
---|
163 | while (line.peek()==' ')
|
---|
164 | line.get();
|
---|
165 |
|
---|
166 | string buf;
|
---|
167 | if (line.peek()=='\"')
|
---|
168 | {
|
---|
169 | line.get();
|
---|
170 | getline(line, buf, '\"');
|
---|
171 | }
|
---|
172 | else
|
---|
173 | line >> buf;
|
---|
174 |
|
---|
175 | EvalImp(i, line, v, buf);
|
---|
176 | }
|
---|
177 |
|
---|
178 | // --------------------------------------------------------------------------
|
---|
179 | //
|
---|
180 | //! Constructs a data block from the given string according to the given
|
---|
181 | //! format. (See also the class reference for more details).
|
---|
182 | //!
|
---|
183 | //! The data block is stored in a vector<char>. It's content can be
|
---|
184 | //! retrieved using the member functions Ptr() and Size(). Whether parsing
|
---|
185 | //! was successfull or not can be checked with GetRc().
|
---|
186 | //! If parsing was not successfull, either the format contained something
|
---|
187 | //! odd, the conversion failed (e.g. 5.5 for an int) or the string contained
|
---|
188 | //! a wrong number of arguments.
|
---|
189 | //!
|
---|
190 | //! @param out
|
---|
191 | //! The ostream to which errors and debug messages should be printed.
|
---|
192 | //!
|
---|
193 | //! @param fmt
|
---|
194 | //! The format descriptor according to the dim definition
|
---|
195 | //!
|
---|
196 | //! @param str
|
---|
197 | //! The string which should be interpreted, e.g. "1 2 5.5 abcdef"
|
---|
198 | //!
|
---|
199 | Converter::Converter(std::ostream &out, const std::string &fmt, const std::string &str)
|
---|
200 | : rc(false), wout(out)
|
---|
201 | {
|
---|
202 | // If the format is empty we are already done
|
---|
203 | if (fmt.empty())
|
---|
204 | {
|
---|
205 | if (!str.empty())
|
---|
206 | {
|
---|
207 | wout << endl;
|
---|
208 | wout << kRed << "Data string not empty as it ought to be!" << endl;
|
---|
209 | return;
|
---|
210 | }
|
---|
211 |
|
---|
212 | wout << endl;
|
---|
213 | rc = true;
|
---|
214 | return;
|
---|
215 | }
|
---|
216 |
|
---|
217 | // Access both, the data and the format through a stringstream
|
---|
218 | stringstream line(str);
|
---|
219 | stringstream stream(fmt);
|
---|
220 |
|
---|
221 | // For better performance we could use sregex
|
---|
222 | static const boost::regex expr("^[ ]*([OBWCSILFDX])[ ]*(:[ ]*([1-9]+[0-9]*))?[ ]*$");
|
---|
223 |
|
---|
224 | // Tokenize the format
|
---|
225 | int arg = 0;
|
---|
226 | string buffer;
|
---|
227 | while (getline(stream, buffer, ';'))
|
---|
228 | {
|
---|
229 | boost::smatch what;
|
---|
230 | if (!boost::regex_match(buffer, what, expr))
|
---|
231 | {
|
---|
232 | wout << endl;
|
---|
233 | wout << kRed << "Wrong format string '" << buffer << "'!" << endl;
|
---|
234 | return;
|
---|
235 | }
|
---|
236 |
|
---|
237 | const string t = what[1]; // type id
|
---|
238 | const string n = what[3]; // counter
|
---|
239 |
|
---|
240 | int cnt = atoi(n.c_str());
|
---|
241 |
|
---|
242 | // Check if the format is just C (without a number)
|
---|
243 | // That would mean that it is a \0 terminated string
|
---|
244 | if (t[0]=='C' && cnt==0)
|
---|
245 | {
|
---|
246 | // Remove leading whitespaces
|
---|
247 | while (line.peek()==' ')
|
---|
248 | line.get();
|
---|
249 |
|
---|
250 | line >> noskipws;
|
---|
251 |
|
---|
252 | const istream_iterator<char> eol; // end-of-line iteartor
|
---|
253 | const string s(istream_iterator<char>(line), eol);
|
---|
254 |
|
---|
255 | vec.push_back(s);
|
---|
256 |
|
---|
257 | data.insert(data.end(), s.begin(), s.end());
|
---|
258 | data.push_back(0);
|
---|
259 |
|
---|
260 | line.clear(ios::eofbit);
|
---|
261 |
|
---|
262 | continue;
|
---|
263 | }
|
---|
264 |
|
---|
265 | // if the :N part was not given assume 1
|
---|
266 | if (cnt==0)
|
---|
267 | cnt=1;
|
---|
268 |
|
---|
269 | // Get as many items from the input line as requested
|
---|
270 | for (int j=0; j<cnt; j++)
|
---|
271 | switch (t[0])
|
---|
272 | {
|
---|
273 | case 'C': // Skip whitespaces when checking for characters
|
---|
274 | if (j>0)
|
---|
275 | line >> noskipws;
|
---|
276 | Eval<char>(arg++, line, data);
|
---|
277 | line >> skipws;
|
---|
278 | break;
|
---|
279 | case 'B': EvalBool (arg++, line, data); break;
|
---|
280 | case 'S': Eval<short> (arg++, line, data); break;
|
---|
281 | case 'I': Eval<int> (arg++, line, data); break;
|
---|
282 | case 'L': Eval<long> (arg++, line, data); break;
|
---|
283 | case 'F': Eval<float> (arg++, line, data); break;
|
---|
284 | case 'D': Eval<double> (arg++, line, data); break;
|
---|
285 | case 'X': Eval<long long>(arg++, line, data); break;
|
---|
286 | case 'W': EvalString (arg++, line, data); break;
|
---|
287 | case 'O': EvalString (arg++, line, data); line.clear(ios::goodbit); break;
|
---|
288 | default:
|
---|
289 | // This should never happen!
|
---|
290 | wout << endl << kRed << "Format '" << t[0] << " not known!" << endl;
|
---|
291 | break;
|
---|
292 | }
|
---|
293 |
|
---|
294 | //wout << "{" << line.eof() << line.good() << line.fail() << "}";
|
---|
295 | if (!line)
|
---|
296 | break;
|
---|
297 | }
|
---|
298 | //wout << "{" << line.eof() << line.good() << line.fail() << "}";
|
---|
299 |
|
---|
300 | wout << " [" << fmt << "]=" << data.size() << endl;
|
---|
301 |
|
---|
302 | // Something wrong with the conversion (e.g. 5.5 for an int)
|
---|
303 | if (line.fail() && !line.eof())
|
---|
304 | {
|
---|
305 | line.clear(); // This is necesasary to get a proper response from tellg()
|
---|
306 | wout << kRed << "Error converting argument at " << arg << " [fmt=" << fmt << "]!" << endl;
|
---|
307 | wout << kRed << str << endl;
|
---|
308 | wout << kRed << setw(int(line.tellg())) << " " << "^" << endl;
|
---|
309 | return;
|
---|
310 | }
|
---|
311 |
|
---|
312 | // Not enough arguments, we have not reached the end
|
---|
313 | if (line.fail() && line.eof())
|
---|
314 | {
|
---|
315 | line.clear();
|
---|
316 | wout << kRed << "Not enough arguments [fmt=" << fmt << "]!" << endl;
|
---|
317 | wout << kRed << str << endl;
|
---|
318 | wout << kRed << setw(int(line.tellg())+1) << " " << "^" << endl;
|
---|
319 | return;
|
---|
320 | }
|
---|
321 |
|
---|
322 | // Too many arguments, we have not reached the end
|
---|
323 | // Unfortunately, this can also mean that there is something
|
---|
324 | // wrong with the last argument
|
---|
325 | if (line.good() && !line.eof())
|
---|
326 | {
|
---|
327 | wout << kRed << "More arguments available than expected [" << fmt << "]!" << endl;
|
---|
328 | wout << kRed << str << endl;
|
---|
329 | wout << kRed << setw(int(line.tellg())+1) << " " << "^" << endl;
|
---|
330 | return;
|
---|
331 | }
|
---|
332 |
|
---|
333 | // Set return code to true (successfull)
|
---|
334 | rc = true;
|
---|
335 | }
|
---|
336 |
|
---|
337 | // --------------------------------------------------------------------------
|
---|
338 | //
|
---|
339 | //! Gets the value as a binary from the ptr and return it as a string.
|
---|
340 | //! The pointer is increased accordingly.
|
---|
341 | //!
|
---|
342 | //! Adds the value to Converter::vec.
|
---|
343 | //!
|
---|
344 | //! @param ptr
|
---|
345 | //! A reference to a pointer to the data which should be converted.
|
---|
346 | //! The pointer is increased according to the size of the data.
|
---|
347 | //!
|
---|
348 | //! @returns
|
---|
349 | //! The data converted into a string
|
---|
350 | //!
|
---|
351 | template<class T>
|
---|
352 | string Converter::Get(const char* &ptr)
|
---|
353 | {
|
---|
354 | const T &t = *reinterpret_cast<const T*>(ptr);
|
---|
355 |
|
---|
356 | vec.push_back(t);
|
---|
357 |
|
---|
358 | ostringstream stream;
|
---|
359 | stream << t;
|
---|
360 | ptr += sizeof(T);
|
---|
361 |
|
---|
362 | return stream.str();
|
---|
363 | }
|
---|
364 |
|
---|
365 | // --------------------------------------------------------------------------
|
---|
366 | //
|
---|
367 | //! Constructs a string from the given data block according to the specified
|
---|
368 | //! format. (See also the class reference for more details).
|
---|
369 | //!
|
---|
370 | //! The resulting string is stored in vector<char> and 0-terminated.
|
---|
371 | //! It can be accessed through Ptr().
|
---|
372 | //!
|
---|
373 | //! If the conversion faild GetRc will return false. In this case the data
|
---|
374 | //! contents might not be well defined.
|
---|
375 | //!
|
---|
376 | //! If no format is given (size == 0) but the data size is finite (>0)
|
---|
377 | //! then the data is converted into a hex representation.
|
---|
378 | //!
|
---|
379 | //! @remark
|
---|
380 | //! In cases of failures the stored data might be inexisting and
|
---|
381 | //! Ptr() might return NULL. If you output NULL to our streams
|
---|
382 | //! they might not show any further output anymore.
|
---|
383 | //!
|
---|
384 | //! @param fmt
|
---|
385 | //! The format descriptor according to the dim definition
|
---|
386 | //!
|
---|
387 | //! @param out
|
---|
388 | //! The ostream to which errors and debug messages should be printed.
|
---|
389 | //!
|
---|
390 | //! @param dat
|
---|
391 | //! Pointer to the start of the binary data
|
---|
392 | //!
|
---|
393 | //! @param size
|
---|
394 | //! Size of the binary data region
|
---|
395 | //!
|
---|
396 | Converter::Converter(ostream &out, const string &fmt, const void *dat, int size)
|
---|
397 | : rc(false), wout(out)
|
---|
398 | {
|
---|
399 | const char *ptr = reinterpret_cast<const char *>(dat);
|
---|
400 |
|
---|
401 | ostringstream text;
|
---|
402 |
|
---|
403 | // Structure: print hex representation
|
---|
404 | if (fmt.size()==0)
|
---|
405 | {
|
---|
406 | if (size==0)
|
---|
407 | {
|
---|
408 | data.push_back(0);
|
---|
409 | rc = true;
|
---|
410 | return;
|
---|
411 | }
|
---|
412 |
|
---|
413 | text << hex;
|
---|
414 |
|
---|
415 | for (int i=0; i<size; i++)
|
---|
416 | text << setw(2) << ptr[i] << " ";
|
---|
417 |
|
---|
418 | const string &ref = text.str();
|
---|
419 | data.insert(data.begin(), ref.begin(), ref.end());
|
---|
420 | data.push_back(0);
|
---|
421 | return;
|
---|
422 | }
|
---|
423 |
|
---|
424 | // Access both, the data and the format through a stringstream
|
---|
425 | stringstream stream(fmt);
|
---|
426 |
|
---|
427 | // For better performance we could use sregex
|
---|
428 | static const boost::regex expr("^[ ]*([CSILFDX])[ ]*(:[ ]*([1-9]+[0-9]*))?[ ]*$");
|
---|
429 |
|
---|
430 | // Tokenize the format
|
---|
431 | string buffer;
|
---|
432 | while (getline(stream, buffer, ';'))
|
---|
433 | {
|
---|
434 | if (ptr-size>=dat)
|
---|
435 | {
|
---|
436 | wout << kRed << "Format description '" << fmt << "' exceeds available data size (" << size << ")" << endl;
|
---|
437 | return;
|
---|
438 | }
|
---|
439 |
|
---|
440 | boost::smatch what;
|
---|
441 | if (!boost::regex_match(buffer, what, expr))
|
---|
442 | {
|
---|
443 | wout << kRed << "Wrong format string '" << buffer << "'!" << endl;
|
---|
444 | return;
|
---|
445 | }
|
---|
446 |
|
---|
447 | const string t = what[1]; // type id
|
---|
448 | const string n = what[3]; // counter
|
---|
449 |
|
---|
450 | int cnt = atoi(n.c_str());
|
---|
451 |
|
---|
452 | // Check if the format is just C (without a number)
|
---|
453 | // That would mean that it is a \0 terminated string
|
---|
454 | if (t[0]=='C' && cnt==0)
|
---|
455 | {
|
---|
456 | const string str(ptr);
|
---|
457 | text << ' ' << str;
|
---|
458 | ptr += str.length()+1;
|
---|
459 |
|
---|
460 | vec.push_back(str);
|
---|
461 |
|
---|
462 | break;
|
---|
463 | }
|
---|
464 |
|
---|
465 | // if the :N part was not given assume 1
|
---|
466 | if (cnt==0)
|
---|
467 | cnt=1;
|
---|
468 |
|
---|
469 | // Get as many items from the input line as requested
|
---|
470 | for (int j=0; j<cnt; j++)
|
---|
471 | {
|
---|
472 | text << ' ';
|
---|
473 |
|
---|
474 | switch (t[0])
|
---|
475 | {
|
---|
476 | case 'C': text << Get<char> (ptr); break;
|
---|
477 | case 'S': text << Get<short> (ptr); break;
|
---|
478 | case 'I': text << Get<int> (ptr); break;
|
---|
479 | case 'L': text << Get<long> (ptr); break;
|
---|
480 | case 'F': text << Get<float> (ptr); break;
|
---|
481 | case 'D': text << Get<double> (ptr); break;
|
---|
482 | case 'X': text << Get<long long>(ptr); break;
|
---|
483 | default:
|
---|
484 | // This should never happen!
|
---|
485 | wout << kRed << "Format '" << t[0] << " not known!" << endl;
|
---|
486 | return;
|
---|
487 | }
|
---|
488 | }
|
---|
489 | }
|
---|
490 |
|
---|
491 | if (ptr-size!=dat)
|
---|
492 | {
|
---|
493 | wout << kRed << "Data block size (" << size << ") doesn't fit format description '" << fmt << "'" << endl;
|
---|
494 | return;
|
---|
495 | }
|
---|
496 |
|
---|
497 | rc = true;
|
---|
498 |
|
---|
499 | const string &ref = text.str();
|
---|
500 | data.insert(data.begin(), ref.begin()+1, ref.end());
|
---|
501 | data.push_back(0);
|
---|
502 | }
|
---|
503 |
|
---|
504 |
|
---|
505 |
|
---|
506 | vector<string> Converter::Regex(const string &expr, const string &line)
|
---|
507 | {
|
---|
508 | const boost::regex reg(expr);
|
---|
509 |
|
---|
510 | boost::smatch what;
|
---|
511 | if (!boost::regex_match(line, what, reg, boost::match_extra))
|
---|
512 | return vector<string>();
|
---|
513 |
|
---|
514 | vector<string> ret;
|
---|
515 | for (unsigned int i=0; i<what.size(); i++)
|
---|
516 | ret.push_back(what[i]);
|
---|
517 |
|
---|
518 | return ret;
|
---|
519 | }
|
---|