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

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