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

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