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

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