// ************************************************************************** /** @class Converter @brief An interpreter to convert a command line into a command plus data memory The Converter class interprets arguments in a string accoring to the given format definition and produces a corresponding memory block from it which can be attached to an event later. The format is given according to the Dim format description: The format parameter specifies the contents of the structure in the form T:N[;T:N]*[;T] where T is the item type: (I)nteger, (C)haracter, (L)ong, (S)hort, (F)loat, (D)ouble, X(tra long==long long) and N is the number of such items. The type alone at the end means all following items are of the same type. Example: "I:3;F:2;C" means 3 Integers, 2 Floats and Characters until the end. The format parameter is used for communicating between different platforms. For example: \code Converter c(cout, "I:1;F:2;I:2", "COMMAND 1 2.5 4.2 3 4"); \endcode would produce a 20 byte data block with the integers 1, the floats 2.5 and 4.2, and the intergers 3 and 4, in this order. The opposite direction is also possible \code Converter c(cout, "I:1;F:2;I:2", pointer, size); \endcode In addition to converting the options from a string into binary format or back all found values are also put into a vector of boost::any objects. They can be accessed using e.g. \code Converter c(cout, "I:1;F:1;B:1;A:1;C", "112 5.5 on this is a test"); cout << c.Get(0) << endl; // prints '112' cout << c.Get(1) << endl; // prints '5.5' cout << c.Get(2) << endl; // prints '1' cout << c.Get(3) << endl; // prints 'this' cout << c.Get(4) << endl; // prints 'is a test' \endcode The format parameter \b W(ord) is dedicated to this kind of conversion and not understood by Dim. In addition there are \b O(ptions) which are like Words but can be omitted. They should only be used at the end of the string. \b B(ool) is also special. It evaluates true/false, yes/no, on/off, 1/0. Access with Converter::Get() is exception safe. Access with Converter::At() would throw an exception if the index is out of bounds or the conversion fails. (This is the prefered method if the type is well known, to easily detect programing faults) @remark Most probably we support more formats than dim does... */ // ************************************************************************** #include "Converter.h" #include #include #include // std::tolower #include // std::transform #include #include "Readline.h" #include "WindowLog.h" using namespace std; template void Converter::EvalImp(int i, std::stringstream &line, std::vector &v, const T &val) { if (!line) wout << " arg[" << i << "]"; else wout << " (" << val << ")"; vec.push_back(val); v.insert(v.end(), reinterpret_cast(&val), reinterpret_cast(&val+1)); } // -------------------------------------------------------------------------- // //! Gets a value of the template type T from the stringstream and adds the //! value as binary data to the end of the vector. If a value couldn't be //! obtained it is set to 0. //! //! Adds the value to Converter::vec. //! //! @param i //! The number of the processed argument (currently used for some debug //! output when an argument is not available and artificially set to 0) //! //! @param line //! The stringstream from which the data should be read //! //! @param vec //! The vector of bytes at which end the data is added in binary format //! template void Converter::Eval(int i, std::stringstream &line, std::vector &v) { T val; line >> val; EvalImp(i, line, v, val); } // -------------------------------------------------------------------------- // //! This functions works similar the the template Eval but is dedicated to //! bools. It evaluates yes/no, on/off, true/false and 1/0. //! //! Sets the failbit of the stream if the "value" is not known. //! //! Adds the value to Converter::vec. //! //! @param line //! The stringstream from which the data should be read //! //! @param vec //! The vector of bytes at which end the data is added in binary format //! //! @returns //! The bool evaluated //! void Converter::EvalBool(int i, std::stringstream &line, std::vector &v) { string buf; line >> buf; transform(buf.begin(), buf.end(), buf.begin(), (int(*)(int)) std::tolower); if (buf=="yes" || buf=="true" || buf=="on" || buf=="1") { EvalImp(i, line, v, bool(true)); return; } if (buf=="no" || buf=="false" || buf=="off" || buf=="0") { EvalImp(i, line, v, bool(false)); return; } line.clear(ios::failbit); } void Converter::EvalString(int i, std::stringstream &line, std::vector &v) { while (line.peek()==' ') line.get(); string buf; if (line.peek()=='\"') { line.get(); getline(line, buf, '\"'); } else line >> buf; EvalImp(i, line, v, buf); } // -------------------------------------------------------------------------- // //! Constructs a data block from the given string according to the given //! format. (See also the class reference for more details). //! //! The data block is stored in a vector. It's content can be //! retrieved using the member functions Ptr() and Size(). Whether parsing //! was successfull or not can be checked with GetRc(). //! If parsing was not successfull, either the format contained something //! odd, the conversion failed (e.g. 5.5 for an int) or the string contained //! a wrong number of arguments. //! //! @param out //! The ostream to which errors and debug messages should be printed. //! //! @param fmt //! The format descriptor according to the dim definition //! //! @param str //! The string which should be interpreted, e.g. "1 2 5.5 abcdef" //! Converter::Converter(std::ostream &out, const std::string &fmt, const std::string &str) : rc(false), wout(out) { // If the format is empty we are already done if (fmt.empty()) { if (!str.empty()) { wout << endl; wout << kRed << "Data string not empty as it ought to be!" << endl; return; } wout << endl; rc = true; return; } // Access both, the data and the format through a stringstream stringstream line(str); stringstream stream(fmt); // For better performance we could use sregex static const boost::regex expr("^[ ]*([OBWCSILFDX])[ ]*(:[ ]*([1-9]+[0-9]*))?[ ]*$"); // Tokenize the format int arg = 0; string buffer; while (getline(stream, buffer, ';')) { boost::smatch what; if (!boost::regex_match(buffer, what, expr)) { wout << endl; wout << kRed << "Wrong format string '" << buffer << "'!" << endl; return; } const string t = what[1]; // type id const string n = what[3]; // counter int cnt = atoi(n.c_str()); // Check if the format is just C (without a number) // That would mean that it is a \0 terminated string if (t[0]=='C' && cnt==0) { // Remove leading whitespaces while (line.peek()==' ') line.get(); line >> noskipws; const istream_iterator eol; // end-of-line iteartor const string s(istream_iterator(line), eol); vec.push_back(s); data.insert(data.end(), s.begin(), s.end()); data.push_back(0); line.clear(ios::eofbit); continue; } // if the :N part was not given assume 1 if (cnt==0) cnt=1; // Get as many items from the input line as requested for (int j=0; j0) line >> noskipws; Eval(arg++, line, data); line >> skipws; break; case 'B': EvalBool (arg++, line, data); break; case 'S': Eval (arg++, line, data); break; case 'I': Eval (arg++, line, data); break; case 'L': Eval (arg++, line, data); break; case 'F': Eval (arg++, line, data); break; case 'D': Eval (arg++, line, data); break; case 'X': Eval(arg++, line, data); break; case 'W': EvalString (arg++, line, data); break; case 'O': EvalString (arg++, line, data); line.clear(ios::goodbit); break; default: // This should never happen! wout << endl << kRed << "Format '" << t[0] << " not known!" << endl; break; } //wout << "{" << line.eof() << line.good() << line.fail() << "}"; if (!line) break; } //wout << "{" << line.eof() << line.good() << line.fail() << "}"; wout << " [" << fmt << "]=" << data.size() << endl; // Something wrong with the conversion (e.g. 5.5 for an int) if (line.fail() && !line.eof()) { line.clear(); // This is necesasary to get a proper response from tellg() wout << kRed << "Error converting argument at " << arg << " [fmt=" << fmt << "]!" << endl; wout << kRed << str << endl; wout << kRed << setw(int(line.tellg())) << " " << "^" << endl; return; } // Not enough arguments, we have not reached the end if (line.fail() && line.eof()) { line.clear(); wout << kRed << "Not enough arguments [fmt=" << fmt << "]!" << endl; wout << kRed << str << endl; wout << kRed << setw(int(line.tellg())+1) << " " << "^" << endl; return; } // Too many arguments, we have not reached the end // Unfortunately, this can also mean that there is something // wrong with the last argument if (line.good() && !line.eof()) { wout << kRed << "More arguments available than expected [" << fmt << "]!" << endl; wout << kRed << str << endl; wout << kRed << setw(int(line.tellg())+1) << " " << "^" << endl; return; } // Set return code to true (successfull) rc = true; } // -------------------------------------------------------------------------- // //! Gets the value as a binary from the ptr and return it as a string. //! The pointer is increased accordingly. //! //! Adds the value to Converter::vec. //! //! @param ptr //! A reference to a pointer to the data which should be converted. //! The pointer is increased according to the size of the data. //! //! @returns //! The data converted into a string //! template string Converter::Get(const char* &ptr) { const T &t = *reinterpret_cast(ptr); vec.push_back(t); ostringstream stream; stream << t; ptr += sizeof(T); return stream.str(); } // -------------------------------------------------------------------------- // //! Constructs a string from the given data block according to the specified //! format. (See also the class reference for more details). //! //! The resulting string is stored in vector and 0-terminated. //! It can be accessed through Ptr(). //! //! If the conversion faild GetRc will return false. In this case the data //! contents might not be well defined. //! //! If no format is given (size == 0) but the data size is finite (>0) //! then the data is converted into a hex representation. //! //! @remark //! In cases of failures the stored data might be inexisting and //! Ptr() might return NULL. If you output NULL to our streams //! they might not show any further output anymore. //! //! @param fmt //! The format descriptor according to the dim definition //! //! @param out //! The ostream to which errors and debug messages should be printed. //! //! @param dat //! Pointer to the start of the binary data //! //! @param size //! Size of the binary data region //! Converter::Converter(ostream &out, const string &fmt, const void *dat, int size) : rc(false), wout(out) { const char *ptr = reinterpret_cast(dat); ostringstream text; // Structure: print hex representation if (fmt.size()==0) { if (size==0) { data.push_back(0); rc = true; return; } text << hex; for (int i=0; i=dat) { wout << kRed << "Format description '" << fmt << "' exceeds available data size (" << size << ")" << endl; return; } boost::smatch what; if (!boost::regex_match(buffer, what, expr)) { wout << kRed << "Wrong format string '" << buffer << "'!" << endl; return; } const string t = what[1]; // type id const string n = what[3]; // counter int cnt = atoi(n.c_str()); // Check if the format is just C (without a number) // That would mean that it is a \0 terminated string if (t[0]=='C' && cnt==0) { const string str(ptr); text << ' ' << str; ptr += str.length()+1; vec.push_back(str); break; } // if the :N part was not given assume 1 if (cnt==0) cnt=1; // Get as many items from the input line as requested for (int j=0; j (ptr); break; case 'S': text << Get (ptr); break; case 'I': text << Get (ptr); break; case 'L': text << Get (ptr); break; case 'F': text << Get (ptr); break; case 'D': text << Get (ptr); break; case 'X': text << Get(ptr); break; default: // This should never happen! wout << kRed << "Format '" << t[0] << " not known!" << endl; return; } } } if (ptr-size!=dat) { wout << kRed << "Data block size (" << size << ") doesn't fit format description '" << fmt << "'" << endl; return; } rc = true; const string &ref = text.str(); data.insert(data.begin(), ref.begin()+1, ref.end()); data.push_back(0); } vector Converter::Regex(const string &expr, const string &line) { const boost::regex reg(expr); boost::smatch what; if (!boost::regex_match(line, what, reg, boost::match_extra)) return vector(); vector ret; for (unsigned int i=0; i