source: trunk/FACT++/src/Converter.cc @ 10278

Last change on this file since 10278 was 10278, checked in by tbretz, 9 years ago
Skipped processing of the terminating entry in the list.
File size: 23.5 KB
Line 
1// **************************************************************************
2/** @class Converter
3
4@brief A compiler for the DIM data format string
5
6The Converter class interprets arguments in a string accoring to the
7given format definition and produces a corresponding memory block from it
8which can be attached to an event later.
9
10The 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
20For example:
21
22\code
23   Converter c(cout, "I:1;F:2;I:2", );
24   vector<char> v = c.GetVector("COMMAND 1 2.5 4.2 3 4");
25\endcode
26
27would produce a 20 byte data block with the integers 1, the floats
282.5 and 4.2, and the intergers 3 and 4, in this order.
29
30The opposite direction is also possible
31
32\code
33   Converter c(cout, "I:1;F:2;I:2");
34   cout << c.GetString(pointer, size) << endl;
35 \endcode
36
37Other conversion functions also exist.
38
39A result of a conversion is valid if valid() returns true and either the
40result of the conversion is empty and the format string was valid empty
41or both contain contents.
42
43The format parameter \b W(ord) is dedicated to this kind of conversion and
44not understood by Dim. In addition there are \b O(ptions) which are like
45Words but can be omitted. They should only be used at the end of the string.
46\b B(ool) is also special. It evaluates true/false, yes/no, on/off, 1/0.
47
48The non-DIM like format options can be switched on and off by using the
49strict argument in the constructor.
50
51*/
52// **************************************************************************
53#include "Converter.h"
54
55#include <iostream>
56#include <iomanip>
57#include <sstream>
58
59#include <cctype>    // std::tolower
60#include <algorithm> // std::transform
61
62#include <boost/regex.hpp>
63
64#include "Readline.h"
65#include "WindowLog.h"
66
67using namespace std;
68/*
69In addition to converting the options from a string into binary format
70or back all found values are also put into a vector of boost::any objects.
71They can be accessed using e.g.
72
73\code
74Converter c(cout, "I:1;F:1;B:1;A:1;C", "112 5.5 on this is a test");
75
76   cout << c.Get<int>(0)    << endl;  // prints '112'
77   cout << c.Get<float>(1)  << endl;  // prints '5.5'
78   cout << c.Get<bool>(2)   << endl;  // prints '1'
79   cout << c.Get<string>(3) << endl;  // prints 'this'
80   cout << c.Get<string>(4) << endl;  // prints 'is a test'
81\endcode
82*/
83
84// --------------------------------------------------------------------------
85//
86//! This function is supposed to remove all whitespaces from the format
87//! string to allow easier regular expressions later.
88//!
89//! @param s
90//!     string to be cleaned
91//!
92//! @returns
93//!     string cleaned from whitespaces
94//
95std::string Converter::Clean(std::string s)
96{
97    while (1)
98    {
99        const size_t pos = s.find_last_of(' ');
100        if (pos==string::npos)
101            break;
102        s.erase(pos, pos+1);
103    }
104
105    return s;
106}
107
108// --------------------------------------------------------------------------
109//
110//! This is just a simplification. For the time being it is used to output
111//! the interpreted contents to the logging stream. Its main purpose
112//! is to add the contents of val in a binary representation to the
113//! vector v
114//!
115//! @tparam
116//!     data type of the variable which should be added
117//!
118//! @param val
119//!     reference to the data
120//!
121//! @param v
122//!     vector<char> to which the binary copy should be added
123//!
124template <class T>
125void Converter::GetBinImp(std::vector<char> &v, const T &val) const
126{
127    wout << " (" << val << ")";
128
129    v.insert(v.end(),
130             reinterpret_cast<const char*>(&val),
131             reinterpret_cast<const char*>(&val+1));
132}
133
134// --------------------------------------------------------------------------
135//
136//! This is just a simplification. For the time being it is used to output
137//! the interpreted contents to the logging stream. Its main purpose
138//! is to add the contents of val as a boost::any object to the
139//! vector v
140//!
141//! @tparam
142//!     data type of the variable which should be added
143//!
144//! @param val
145//!     reference to the data
146//!
147//! @param v
148//!     vector<boost::any> to which the value should be added
149//!
150template <class T>
151void Converter::GetBinImp(std::vector<boost::any> &v, const T &val) const
152{
153    wout << " (" << val << ")";
154
155    v.push_back(val);
156}
157
158// --------------------------------------------------------------------------
159//
160//! This is just a simplification. For the time being it is used to output
161//! the interpreted contents to the logging stream. Its main purpose
162//! is to add the contents of the provided string at the end of the vector v.
163//! vector v
164//!
165//! @param val
166//!     reference to the string
167//!
168//! @param v
169//!     vector<char> to which the value should be added
170//!
171void Converter::GetBinString(std::vector<char> &v, const string &val) const
172{
173    wout << " (" << val << ")";
174
175    v.insert(v.end(), val.begin(), val.end());
176}
177
178// --------------------------------------------------------------------------
179//
180//! This is just a simplification. For the time being it is used to output
181//! the interpreted contents to the logging stream. Its main purpose
182//! is to add the contents of the provided string at the end of the vector v.
183//! vector v
184//!
185//! @param val
186//!     reference to the string
187//!
188//! @param v
189//!     vector<boost::any> to which the value should be added
190//!
191void Converter::GetBinString(std::vector<boost::any> &v, const string &val) const
192{
193    wout << " (" << val << ")";
194
195    v.push_back(val);
196}
197
198// --------------------------------------------------------------------------
199//
200//! Converts from the stringstream into the provided type.
201//!
202//! @param line
203//!     reference to the stringstream from which the data should be
204//!     interpreted
205//!
206//! @tparam
207//!     Type of the data to be returned
208//!
209//! @returns
210//!     The interpreted data
211//!
212template <class T>
213T Converter::Get(std::stringstream &line) const
214{
215    T val;
216    line >> val;
217    return val;
218}
219
220// --------------------------------------------------------------------------
221//
222//! Converts from the stringstream into bool. It allows to use lexical
223//! boolean representations like yes/no, on/off, true/false and of
224//! course 0/1. If the conversion fails the failbit is set.
225//!
226//! @param line
227//!     reference to the stringstream from which the data should be
228//!     interpreted
229//!
230//! @returns
231//!     The boolean. 0 in case of failure
232//!
233bool Converter::GetBool(std::stringstream &line) const
234{
235    string buf;
236    line >> buf;
237    transform(buf.begin(), buf.end(), buf.begin(), (int(*)(int)) std::tolower);
238
239    if (buf=="yes" || buf=="true" || buf=="on" || buf=="1")
240        return true;
241
242    if (buf=="no" || buf=="false" || buf=="off" || buf=="0")
243        return false;
244
245    line.clear(ios::failbit);
246
247    return false;
248}
249
250// --------------------------------------------------------------------------
251//
252//! Converts from the stringstream into a string. Leading whitespaces are
253//! skipped. Everything up to the next whitespace is returned.
254//! strings can be encapsulated into escape characters ("). Note, that
255//! they cannot be nested.
256//!
257//! @param line
258//!     reference to the stringstream from which the data should be
259//!     interpreted
260//!
261//! @returns
262//!     The string
263//!
264string Converter::GetString(std::stringstream &line) const
265{
266    while (line.peek()==' ')
267        line.get();
268
269    string buf;
270    if (line.peek()=='\"')
271    {
272        line.get();
273        getline(line, buf, '\"');
274    }
275    else
276        line >> buf;
277
278    return buf;
279}
280
281// --------------------------------------------------------------------------
282//
283//! Converts from the stringstream into a string. Leading whitespaces are
284//! skipped. Everything until the end-of-line is returned. A trailing
285//! \0 is added.
286//!
287//! @param line
288//!     reference to the stringstream from which the data should be
289//!     interpreted
290//!
291//! @returns
292//!     The string
293//!
294string Converter::GetStringEol(stringstream &line) const
295{
296    // Remove leading whitespaces
297    while (line.peek()==' ')
298        line.get();
299
300    line >> noskipws;
301
302    const istream_iterator<char> eol; // end-of-line iterator
303    const string s(istream_iterator<char>(line), eol);
304    return s + '\0';
305}
306
307// --------------------------------------------------------------------------
308//
309//! Converts from a binary block into a string. The type of the expected
310//! value is defined by the template parameter.
311//!
312//! @param ptr
313//!     A refrenece to the pointer of the binary representation to be
314//!     interpreted. The pointer is incremented by the sizeof the type.
315//!
316//! @tparam T
317//!     Expected type
318//!
319//! @returns
320//!     The string
321//!
322template<class T>
323string Converter::GetString(const char* &ptr) const
324{
325    const T &t = *reinterpret_cast<const T*>(ptr);
326
327    ostringstream stream;
328    stream << t;
329    ptr += sizeof(T);
330
331    return stream.str();
332}
333
334// --------------------------------------------------------------------------
335//
336//! Converts from a binary block into a hex representation.
337//!
338//! @param ptr
339//!     Pointer to the data block
340//!
341//! @param size
342//!     Size of the data block
343//!
344//! @returns
345//!     The string
346//!
347string Converter::GetHex(const void *dat, int size)
348{
349    const char *ptr = reinterpret_cast<const char *>(dat);
350
351    ostringstream text;
352
353    text << hex;
354
355    for (int i=0; i<size; i++)
356        text << setw(2) << ptr[i] << " ";
357
358    return text.str();
359}
360
361// --------------------------------------------------------------------------
362//
363//! Convert the pointer using GetString into a string and add it (prefixed
364//! by a whaitespace) to the given string.
365//!
366//! @param str
367//!     Reference to the string to which the ptr should be added
368//!
369//! @param ptr
370//!     Pointer to the binary representation. It will be incremented
371//!     according to the sze of the template argument
372//!
373//! @tparam T
374//!     Type as which the binary data should be interpreted
375//!
376template<class T>
377void Converter::Add(string &str, const char* &ptr) const
378{
379    str += ' ' + GetString<T>(ptr);
380}
381
382// --------------------------------------------------------------------------
383//
384//! Convert the pointer into a boost::any object and add it to the
385//! provided vector
386//!
387//! @param vec
388//!     Vector to which the boost::any object should be added
389//!
390//! @param ptr
391//!     Pointer to the binary representation. It will be incremented
392//!     according to the sze of the template argument
393//!
394//! @tparam T
395//!     Type as which the binary data should be interpreted
396//!
397template<class T>
398void Converter::Add(vector<boost::any> &vec, const char* &ptr) const
399{
400    vec.push_back(*reinterpret_cast<const T*>(ptr));
401    ptr += sizeof(T);
402}
403
404// --------------------------------------------------------------------------
405//
406//! Add the string pointed to by ptr to the given string.
407//!
408//! @param str
409//!     Reference to the string to which the ptr should be added
410//!
411//! @param ptr
412//!     Pointer to the binary representation.
413//!
414void Converter::AddString(string &str, const string &ptr) const
415{
416    str += ' ' + ptr;
417}
418
419// --------------------------------------------------------------------------
420//
421//! Add the string pointed to by ptr as boost::any to the provided vector
422//!
423//! @param vec
424//!     Vector to which the boost::any object should be added
425//!
426//! @param ptr
427//!     Pointer to the binary representation of the string.
428//!
429void Converter::AddString(vector<boost::any> &vec, const string &ptr) const
430{
431    vec.push_back(ptr);
432}
433
434// --------------------------------------------------------------------------
435//
436//! Compiles the format string into fList. See Compile() for more details.
437//!
438//! @param out
439//!     Output stream to which possible logging is redirected
440//!
441//! @param fmt
442//!     Format to be compiled. For details see class reference
443//!
444//! @param strict
445//!     Setting this to true allows non DIM options, whiel false
446//!     will restrict the possible format strings to the ones also
447//!     understood by DIM.
448//!
449Converter::Converter(std::ostream &out, const std::string &fmt, bool strict)
450: wout(out), fFormat(Clean(fmt)), fList(Compile(out, fmt, strict))
451{
452}
453
454// --------------------------------------------------------------------------
455//
456//! Compiles the format string into fList.
457//!
458//! Output by default is redirected to cout.
459//!
460//! @param fmt
461//!     Format to be compiled. For details see class reference
462//!
463//! @param strict
464//!     Setting this to true allows non DIM options, whiel false
465//!     will restrict the possible format strings to the ones also
466//!     understood by DIM.
467//!
468Converter::Converter(const std::string &fmt, bool strict)
469: wout(cout), fFormat(Clean(fmt)), fList(Compile(fmt, strict))
470{
471}
472
473// --------------------------------------------------------------------------
474//
475//! Converts the provided format string into a vector.
476//!
477//! @tparam T
478//!     Kind of data to be returned. This can either be boost::any objects
479//!     or a bnary data-block (char).
480//!
481//! @param fmt
482//!     Format to be compiled. For details see class reference
483//!
484//! @returns
485//!    A vector of the given template type containing the arguments. In
486//!    case of failure an empty vector is returned.
487//!
488template <class T>
489vector<T> Converter::Get(const std::string &str) const
490{
491    if (!valid())
492        return vector<T>();
493
494    // If the format is empty we are already done
495    if (empty())
496    {
497        if (str.empty())
498        {
499            wout << endl;
500            return vector<T>();
501        }
502
503        wout << endl;
504        wout << kRed << "Data string not empty as it ought to be!" << endl;
505        return vector<T>();
506    }
507
508    int arg = 0;
509    stringstream line(str);
510
511    vector<T> data;
512
513    for (Converter::FormatList::const_iterator i=fList.begin(); i<fList.end()-1; i++)
514    {
515        if (*i->first.first == typeid(string))
516        {
517            GetBinString(data, GetStringEol(line));
518            line.clear(ios::eofbit);
519            continue;
520        }
521
522        // Get as many items from the input line as requested
523        for (int j=0; j<i->second.first; j++)
524        {
525            cout << j << ": " << i->first.first->name() << endl;
526            switch (i->first.first->name()[0])
527            {
528            case 'c': // Skip whitespaces when checking for characters
529                if (j>0)
530                    line >> noskipws;
531                GetBinImp(data, Get<char>(line));
532                line >> skipws;
533                break;
534            case 'b': GetBinImp(data, GetBool(line)); break;
535            case 's': GetBinImp(data, Get<short>    (line)); break;
536            case 'i': GetBinImp(data, Get<int>      (line)); break;
537            case 'l': GetBinImp(data, Get<long>     (line)); break;
538            case 'f': GetBinImp(data, Get<float>    (line)); break;
539            case 'd': GetBinImp(data, Get<double>   (line)); break;
540            case 'x': GetBinImp(data, Get<long long>(line)); break;
541            case 'N':
542                GetBinString(data, GetString(line));
543                if (*i->first.first == typeid(O))
544                    line.clear(ios::goodbit);
545                break;
546            default:
547                // This should never happen!
548                wout << endl << kRed << "Format '" << i->first.first->name() << " not supported!" << endl;
549                break;
550            }
551
552            arg++;
553        }
554
555        //wout << "{" << line.eof() << line.good() << line.fail() << "}";
556        if (!line)
557            break;
558    }
559
560    wout << endl;
561
562    // Something wrong with the conversion (e.g. 5.5 for an int)
563    if (line.fail() && !line.eof())
564    {
565        line.clear(); // This is necesasary to get a proper response from tellg()
566        wout << kRed << "Error converting argument at " << arg << " [fmt=" << fFormat << "]!" << endl;
567        wout << kRed << line.str() << endl;
568        wout << kRed << setw(int(line.tellg())) << " " << "^" << endl;
569        return vector<T>();
570    }
571
572    // Not enough arguments, we have not reached the end
573    if (line.fail() && line.eof())
574    {
575        line.clear();
576        wout << kRed << "Not enough arguments [fmt=" << fFormat << "]!" << endl;
577        wout << kRed << line.str() << endl;
578        wout << kRed << setw(int(line.tellg())+1) << " " << "^" << endl;
579        return vector<T>();
580    }
581
582    // Too many arguments, we have not reached the end
583    // Unfortunately, this can also mean that there is something
584    // wrong with the last argument
585    if (line.good() && !line.eof())
586    {
587        wout << kRed << "More arguments available than expected [" << fFormat << "]!" << endl;
588        wout << kRed << line.str() << endl;
589        wout << kRed << setw(int(line.tellg())+1) << " " << "^" << endl;
590        return vector<T>();
591    }
592
593    return data;
594
595}
596
597std::vector<boost::any> Converter::GetAny(const std::string &str) const
598{
599    return Get<boost::any>(str);
600}
601
602std::vector<char> Converter::GetVector(const std::string &str) const
603{
604    return Get<char>(str);
605}
606
607
608// --------------------------------------------------------------------------
609//
610//! Converts the provided data block into a vector of boost::any or
611//! a string.
612//!
613//! @tparam T
614//!     Kind of data to be returned. This can either be boost::any objects
615//!     or a string
616//!
617//! @returns
618//!    A vector of the given template type containing the arguments. In
619//!    case of failure an empty vector is returned.
620//!
621template<class T>
622T Converter::Get(const void *dat, int size) const
623{
624    if (!valid())
625        return T();
626
627    const char *ptr = reinterpret_cast<const char *>(dat);
628
629    T text;
630    for (Converter::FormatList::const_iterator i=fList.begin(); i<fList.end()-1; i++)
631    {
632        if (ptr-size>=dat)
633        {
634            wout << kRed << "Format description '" << fFormat << "' exceeds available data size (" << size << ")" << endl;
635            return T();
636        }
637
638        if (*i->first.first == typeid(string))
639        {
640            const string str(ptr);
641            AddString(text, str);
642            ptr += str.length()+1;
643            break;
644        }
645
646        // Get as many items from the input line as requested
647        for (int j=0; j<i->second.first; j++)
648        {
649            switch (i->first.first->name()[0])
650            {
651            case 'b': Add<bool>     (text, ptr); break;
652            case 'c': Add<char>     (text, ptr); break;
653            case 's': Add<short>    (text, ptr); break;
654            case 'i': Add<int>      (text, ptr); break;
655            case 'l': Add<long>     (text, ptr); break;
656            case 'f': Add<float>    (text, ptr); break;
657            case 'd': Add<double>   (text, ptr); break;
658            case 'x': Add<long long>(text, ptr); break;
659            case 'v':
660                // This should never happen!
661                wout << kRed << "Type 'void' not supported!" << endl;
662                return T();
663            default:
664                // This should never happen!
665                wout << kRed << "TypeId '" << i->first.first->name() << "' not known!" << endl;
666                return T();
667            }
668        }
669    }
670
671    if (ptr-size!=dat)
672    {
673        wout << kRed << "Data block size (" << size << ") doesn't fit format description '" << fFormat << "'" << endl;
674        return T();
675    }
676
677    return text;
678}
679
680std::vector<boost::any> Converter::GetAny(const void *dat, int size) const
681{
682    return Get<vector<boost::any>>(dat, size);
683}
684
685std::vector<char> Converter::GetVector(const void *dat, int size) const
686{
687    const string ref = GetString(dat, size);
688
689    vector<char> data;
690    data.insert(data.begin(), ref.begin()+1, ref.end());
691    data.push_back(0);
692
693    return data;
694}
695
696string Converter::GetString(const void *dat, int size) const
697{
698    const string s = Get<string>(dat, size);
699    return s.empty() ? s : s.substr(1);
700}
701
702template<class T>
703Converter::Type Converter::GetType()
704{
705    Type t;
706    t.first  = &typeid(T);
707    t.second = sizeof(T);
708    return t;
709}
710
711template<class T>
712Converter::Type Converter::GetVoid()
713{
714    Type t;
715    t.first  = &typeid(T);
716    t.second = 0;
717    return t;
718}
719
720// --------------------------------------------------------------------------
721//
722//! static function to compile a format string.
723//!
724//! @param out
725//!     Output stream to which possible logging is redirected
726//!
727//! @param fmt
728//!     Format to be compiled. For details see class reference
729//!
730//! @param strict
731//!     Setting this to true allows non DIM options, whiel false
732//!     will restrict the possible format strings to the ones also
733//!     understood by DIM.
734//!
735Converter::FormatList Converter::Compile(std::ostream &out, const std::string &fmt, bool strict)
736{
737    ostringstream text;
738
739    // Access both, the data and the format through a stringstream
740    stringstream stream(fmt);
741
742    // For better performance we could use sregex
743    static const boost::regex expr1("^([CSILFDXBOW])(:([1-9]+[0-9]*))?$");
744    static const boost::regex expr2("^([CSILFDX])(:([1-9]+[0-9]*))?$");
745
746    FormatList list;
747    Format   format;
748
749    // Tokenize the format
750    string buffer;
751    while (getline(stream, buffer, ';'))
752    {
753        boost::smatch what;
754        if (!boost::regex_match(buffer, what, strict?expr2:expr1))
755        {
756            out << kRed << "Wrong format string '" << buffer << "'!" << endl;
757            return FormatList();
758        }
759
760        const string t = what[1]; // type id
761        const string n = what[3]; // counter
762
763        const int cnt = atoi(n.c_str());
764
765        // if the :N part was not given assume 1
766        format.second.first = cnt == 0 ? 1: cnt;
767
768        // Check if the format is just C (without a number)
769        // That would mean that it is a \0 terminated string
770        if (t[0]=='C' && cnt==0)
771        {
772            format.first = GetType<string>();
773            list.push_back(format);
774            format.second.second = 0; // end position not known
775            break;
776        }
777
778        // Get as many items from the input line as requested
779        switch (t[0])
780        {
781        case 'B':  format.first = GetType<bool>();      break;
782        case 'C':  format.first = GetType<char>();      break;
783        case 'S':  format.first = GetType<short>();     break;
784        case 'I':  format.first = GetType<int>();       break;
785        case 'L':  format.first = GetType<long>();      break;
786        case 'F':  format.first = GetType<float>();     break;
787        case 'D':  format.first = GetType<double>();    break;
788        case 'X':  format.first = GetType<long long>(); break;
789        case 'O':  format.first = GetVoid<O>();         break;
790        case 'W':  format.first = GetVoid<W>();         break;
791        default:
792            // This should never happen!
793            out << kRed << "Format '" << t[0] << " not known!" << endl;
794            return list;
795        }
796
797        list.push_back(format);
798        format.second.second += format.first.second * format.second.first;
799    }
800
801    format.first = GetVoid<void>();
802    format.second.first = 0;
803
804    list.push_back(format);
805
806    return list;
807}
808
809// --------------------------------------------------------------------------
810//
811//! Same as Compile(ostream&,string&,bool) but cout is used as the default
812//! output stream.
813//!
814//!
815Converter::FormatList Converter::Compile(const std::string &fmt, bool strict)
816{
817    return Compile(cout, fmt, strict);
818}
819
820vector<string> Converter::Regex(const string &expr, const string &line)
821{
822    const boost::regex reg(expr);
823
824    boost::smatch what;
825    if (!boost::regex_match(line, what, reg, boost::match_extra))
826        return vector<string>();
827
828    vector<string> ret;
829    for (unsigned int i=0; i<what.size(); i++)
830        ret.push_back(what[i]);
831
832    return ret;
833}
Note: See TracBrowser for help on using the repository browser.