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

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