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

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