Hi all,

I've written a configuration file parser, which is just basically gets a bunch of rules, each ended by ';'

To get the rules I use a loop:

ifstream cfgFile;
for (int iI = 0; iI < iMAX_MONITORS; iI ++) {
   ...
   cfgFile >> someFloat;
   // test for error
   cfgFile >> someInt;
   // test for error
   ...
}

and so on, pretty standard stuff. The problem is, every time I detect an error, all I can do is print the rule number, and not the line number, which means I have to then count the rules by hand to find the erroneous line, because >> ignores whitespace. Is there any way around this short of rewriting the function to use getline?

Is there some stream builtin that will give me a character number, or line number where I am up to in the stream?

All I can think of currently is to use a macro to get the input, and check for '\n':

(I've removed the \ to make it look nicer, and I haven't tested it, but you get the idea:)

#define EXPECT_INPUT(input, msg)
{
   cfgFile >> input;
   if (!cfgFile) {
      if (cfgFile.eof ()) {
         break;
      }
      printf ("error \"%s\" in config file, rule %d, line %d.", msg, iI + 1, iLines + 1);
      cfgFile.clear();
      cfgFile.ignore(std::numeric_limits<streamsize>::max(),';');
      break;
   }
   do {
      c=cfgFile.peek();
      if (c == '\n') iLines ++;
      if (c==' ' || c=='\n') c=cfgFile.get();
   } while (c==' ' || c=='\n');
}

and then just do:

EXPECT_INPUT (someFloat, "a number");
EXPECT_INPUT (charArray, "a string");

which makes my function look nice, except that I have to use an ugly macro. Mayby I should just use getline - thoughts?

thanks heaps.

Comments
At last, a reasonable and well presented question - thanks

Step 1 would be to redo that horrible macro as a proper C++ function.

There are very few valid reasons (struggling to think of one ATM) where using a function-like macro is the best approach. Inline functions and templates cover a lot of the things which were done as macros in C.

Step 2: Then consider implementing a small configFile class, which hides some of the detail (how the file is read), and can provide line:column information via a suitable member function.

Whether you then use getline(), or the code you have then no longer matters to the bulk of the code.

You could then implement other value services, say read a rule.

OK I re-wrote the macro as a function template, however I came into a problem, which the macro didn't have:

I passed as an arg to the macro an extra "test":

#define EXPECT_INPUT(input, test)
{
   cfgFile >> input;
   if (!cfgFile || test) {
      if (cfgFile.eof ()) {
         break;
      }
...

and then for the macro call I could say: EXPECT_INPUT(someChar, (someChar != 'a')); and the test would be done inline, but I can't do that with a function, because the arguement is evaluated before it's passed:

template <class input>
     void HandleInput (ifstream *poF, input &oI, bool bTest2 = false)
      {
         (*poF) >> oI;
      ...
      }
...
void HandleInput (cfgFile, someChar, someChar != 'a');

How do I overcome this? Don't move the test to the function template? Seems like a bit of code duplication.

Also, it still hasn't overcome how I detect a '\n' and hence count lines!

Do I use tellg()? Or the concept from my original macro of sucking up and testing whitespace, but in the function template?

Your brutal honesty is appreciated ;)

thanks again.

OK, the "test" is easy, I can just set up a parameter with a default value

template <class input>
  void HandleInput (ifstream *poF, input &oI, char test = '\0')
   ...
   if (test != '\0')
      ...

as it turns out all my tests were only when I am reading a single character. But still not sure about a more elegant way of detecting '\n'.

>> and then for the macro call I could say ... and the test would be done inline,
>> but I can't do that with a function, because the arguement is evaluated before it's passed:

use a function object (or a pointer to a function; not as flexible or efficient) to encapsulate a unit of procedural code.

>> still not sure about a more elegant way of detecting '\n'.

use composition; wrap a helper class which counts lines around the stream.

#include <iostream>
#include <fstream>
#include <functional>
using namespace std ;

template< typename ISTREAM > struct line_count_wrapper 
{
  explicit line_count_wrapper(  ISTREAM& s ) : stm(s), line(1) {}
  int current_line() const { return line ; }
  template< typename T > line_count_wrapper<ISTREAM>& operator>> ( T& v )
  {
    eatwhite() ;
    stm >> v ;
    return *this ;
  }
  bool operator!() { return !stm ; }
  operator void* () { return stm ; }
  private:
    int line ;
    ISTREAM& stm ;
    void eatwhite()
    {
      char ch ;
      while( stm.get(ch) && isspace(ch) ) 
        if( ch == '\n' ) ++line ;
      if(stm) stm.putback(ch) ;
    }
    // non-copyable; do do not define these
    line_count_wrapper( const line_count_wrapper& ) ;
    void operator= ( const line_count_wrapper& ) ;
};

template< typename LCSTM, typename T, typename TEST > inline 
bool handle_input( LCSTM& stm, T& input, TEST test_error, const char* msg )
{
  if(!( stm >> input ) ) return false ;
  if ( test_error(input) ) 
  {
   cerr << "error " << msg << " at line " << stm.current_line() << '\n' ;
   return false ;
  }
  return true ;
}

int main()
{
  int i ;
  double d ;
  ifstream file( "config.txt" ) ;
  line_count_wrapper<istream> stm(file) ;
  while( stm )
  {
    handle_input( stm, i, bind2nd( equal_to<int>(), 0 ), "int is zero" ) ;
    handle_input( stm, d, bind2nd( less<double>(), 0.0 ), "double is -ve" ) ;
  }
}
/**  config.txt
45 89.3
34 -23.7
0 
56.7
0 -34.8 23 
42.5 
8 -67.2
*/
/** output
error double is -ve at line 2
error int is zero at line 3
error int is zero at line 5
error double is -ve at line 5
error double is -ve at line 7
*/

you could use boost.iostream and boost.lambda; the code would then look more elegant.

Isn't looking for '\n' going to have problems on different os. I mean isn't newlines defined differently for macs, linux and windows operating systems?

c provides escape sequences '\n' and '\r'. contrary to popular belief, these are not required to be equivalent to specific control characters. the c standard guarantees that 1. each of these escape sequences maps to a unique implementation-defined number that can be stored in a single char. 2. when writing a file in text mode, '\n' is transparently translated to the native newline sequence on the the system (which may be longer than one character). when reading in text mode, the native newline sequence is translated back to '\n'. (in binary i/o mode, no such translation is performed.) c++ also provides L'\n' and L'\r'; these are the wchar_t equivalents.

so, looking for '\n' is correct if the char_type of the stream is a char. to also handle other char_types (eg. wchar_t), basic_ios<char_type,traits_types>::widen('\n') could be used.

Comments
Good answer regarding character translations.

using '\n' is ok if we are sure that the char_type of the stream is a char. to also take care of all char_types (including user-defined char_types), we should modify the code to

void eatwhite()
{
   typedef typename ISTREAM::char_type char_type ;
   ctype<char_type> facet = 
          use_facet< ctype<char_type> >( stm.getloc() ) ;
   char_type ch ;
   while( stm.get(ch) && facet.is( ctype_base::space, ch ) ) 
        if( ch == facet.widen('\n') ) ++line ;
   if(stm) stm.putback(ch) ;
 }

this may not be very appropriate code to show to a student who is just starting out with c++.

I can't seem to get your example to work, what am I doing wrong?

thwee@thwee-desktop:~$ g++ -Wall pedantic.cc
pedantic.cc: In constructor ‘line_count_wrapper<ISTREAM>::line_count_wrapper(ISTREAM&) [with ISTREAM = std::basic_istream<char, std::char_traits<char> >]’:
pedantic.cc:50:   instantiated from here
pedantic.cc:20: warning: ‘line_count_wrapper<std::basic_istream<char, std::char_traits<char> > >::stm’ will be initialized after
pedantic.cc:19: warning:   ‘int line_count_wrapper<std::basic_istream<char, std::char_traits<char> > >::line’
pedantic.cc:8: warning:   when initialized here

??

the constructor of line_count_wrapper<ISTREAM> is explicit line_count_wrapper( ISTREAM& s ) : stm(s), line(1) {} the order of declaration of members in line_count_wrapper<ISTREAM> is

private:
    int line ;
    ISTREAM& stm ;

the compiler is warning you that the order of initialization is in order of declaration ie. line(1), stm(s) and not the order that is specified in the constructor initializer list. you can make the warning go away by changing either the order of declaration or the order in the constructor initializer list.
to use facets, you should also have a #include<locale>

This article has been dead for over six months. Start a new discussion instead.