/*
cxe_inputline.cc
Initial Sun Mar 24 13:20:12 PST 2002
Ok, I'm tired of the C++ library struggle. I tried to find and learn
to use C++ "standard" library functions for various important things,
but it seems like there is no concept of "standard" for C++ - at least
not Linux C++. My new strategy is to create my own useful C++
programming libraries as I need them. I have resigned myself to the
fact that I will have to use purely (old) C functions to get what I
want, but I realize that I can wrap them up in functional classes that
can give the appearance and smoothness of pure C++ and do exactly what
I want in my real programs.

This class will be to solve a huge glaring hole in what C++ has to
offer - input. Since the C++ stream and string libraries are
totally undocumented and unreliable on my system, I will have a C++
class that uses a hybrid of the old C functions to do important things
like input file parsing and command line parsing. 

In this version, the file is opened by the constructor and
closed by the destructor. This causes all kinds of headaches (largely
cured now thanks to Dr.Seth). The alternative is to have separate open
and close member functions.
*/


#include "cxe_inputline.h"

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* **********  class cxe_inputline - stupid input tricks ********** */
/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* Standard constructor */
cxe_inputline::cxe_inputline()
{ /*should naturally work out*/ }

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* Constructor with a text preload */
cxe_inputline::cxe_inputline(char* preload)
{ strcpy(datatext, preload);
  wordposnow=datatext;
 }

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* Standard destructor */
cxe_inputline::~cxe_inputline()
{ /*should naturally work out*/ }

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* Print the dataline as text raw with no modification */
void cxe_inputline::print(ostream *os)
{ *os << datatext; }

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* checkfor
Yes/no whether the dataline contains any of the supplied characters.
Good for checking if there's anything objectionable in a line.
example:
if ( var.checkfor("#@!") ) cout << "Probably a comment.\n";     */

bool cxe_inputline::checkfor(char *suspects)
{ unsigned int len=strlen(datatext);
return
 (( ( len == strcspn( datatext, suspects )) || ( !len ) ) ? 0 : 1);
/* the search for chars went to the end (==len) or the string was a
length of 0 (!len) - either way, all innocent of being suspects 0 */
 /* or else we must have found a suspect! 1 */
}

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* strip
Replaces any of the supplied chars with spaces. Any character in the
data that also exists in the string of bogus characters is blanked.
This isn't just for output - it changes the line permanently.
example:
cout << "No longer , : nor ; delimted:" << var.strip(",:;") << endl;
 */

char* cxe_inputline::strip(char *bogus)
{ unsigned int c; for ( c=0 ; c < strlen(datatext) ; c++)
 { if ( strchr(bogus, datatext[c]) ) datatext[c] = ' '; }
return datatext;
}
/* I don't know why I can't return *this, but it causes badness... */
/* Just as well, it's more useful returning the text anyway. */

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* stripallbut
Replaces any of the supplied chars with spaces. Any character in the
data that also exists in the string of bogus characters is blanked.
This isn't just for output - it changes the line permanently.
example:
var.stripallbut("0123456789"); cout << "Only #s now:" << var << endl;
 */
char* cxe_inputline::stripallbut(char *goodchars)
{ unsigned int c; for ( c=0 ; c < strlen(datatext) ; c++)
 { if ( !strchr(goodchars, datatext[c]) ) datatext[c] = ' '; }
return datatext;
}

/* I don't know why I can't return *this, but it causes badness... */
/* Just as well, it's more useful returning the text anyway. */

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* wordcount
Counts the number of words delimited by whitespace sequences.
example:
cout << var.wordcount() << " - " << wordcount << endl;
7 - This song is just six words long.
 */
unsigned int cxe_inputline::wordcount()
{ unsigned int words=0, toggle=0, c;
  for ( c=0 ; c < strlen(datatext) ; c++)
  {
   if ( !( ( datatext[c] == ' ') ^ toggle ) )
    { toggle = !toggle; if (toggle) words++; }
  }

return words; }
 
/* !(A^B) = NXOR (0 0 - 1, 1 0 - 0, 0 1 - 0, 1 1 - 1) */
/* The toggle keeps track of runs of stuff or runs of whitespace. Its
 * value follows the answer to the question "are we on whitespace?".
 * By following this answer, it can detect when that answer changes
 * which is essentially the clue to a new word.
 * When toggle is on (1) the scan is on a word run. When it is (0),
 * then the scan is on a whitespace run. Therefore when it makes the
 * toggle (1 to 0 or 0 to 1), it checks to see if it has hit a new
 * word run (1). If so, then word gets incremented and that's
 * wordcount.*/

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* getnextastext
Gets the next space/eol delimted word and stores it as text into the
supplied string. It also returns that value.
example:
char newword[50]; unsigned int c;
for (c=0; c < goodfile.wordcount() ; c++)
{ cout << "Word " << c+1 << ":" << goodfile.getnextastext(newword) << endl;
*/

char* cxe_inputline::getnextastext(char *wordspace)
{ bool toggle=0; unsigned int c=0;
  wordspace[0] = '\0';
  while ( c < (strlen(wordposnow) + 1) ) {
   if ( wordposnow[c]==' ' || wordposnow[c]=='\0' ) {    
/* space after chars signaling the end of the word! Get the word */
    if (toggle) { 
         strncpy(wordspace, wordposnow, c);
         wordspace[c] = '\0';
         wordposnow+=c; wordindex++;
         c=(strlen(wordposnow)+1);  // make the while loop fail
         } // end if there was a word (toggle=1)
/* Must be pre word space or term- move along to ignore it and reset c */
    else      // no toggle
        if ( wordposnow[c]==' ' ) 
          /* case ' ' - move it along */
          { wordposnow++; c=0; }
        else
          /* case '\0' - set it up for disappointment in the while loop */
          { c=(strlen(wordposnow)+1); }
       } // end space contingency
/*  We're on a char not a space or term. Turn on the toggle. */
   else  { toggle=1; c++; }
} // end of while loop

return wordspace;
}

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* getnextasfloat
Gets the next space/eol delimted word and returns it as a float if
possible. If it can not be resolved into a float (see atof), it
returns a value of 0.
example:
unsigned int c; for (c=0; c < goodfile.wordcount() ; c++)
{ cout << "# word " << c+1 << ":" << goodfile.getnextasfloat() << endl;
*/

float cxe_inputline::getnextasfloat()
{ char tempwordspace[500];
  getnextastext(tempwordspace);
  return atof(tempwordspace);  
}

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* getnextasint
Gets the next space/eol delimted word and returns it as an int if
possible. If it can not be resolved into an int (see atoi), it
returns a value of 0.
example:
unsigned int c; for (c=0; c < goodfile.wordcount() ; c++)
{ cout << "# word " << c+1 << ":" << goodfile.getnextasint() << endl;
*/

int cxe_inputline::getnextasint()
{ char tempwordspace[500];
  getnextastext(tempwordspace);
  return atoi(tempwordspace);  
}

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
ostream &operator<<(ostream &os, cxe_inputline &P)
{ P.print(&os); return os; }

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* **********  class cxe_finputline - FILE management ************* */
/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
cxe_finputline::cxe_finputline()
{ 
/* This is a constructor without a file - implies cin/stdin */
/* A null pointer in source will be the clue for cin/stdin ops */
 source = 0;
}

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
cxe_finputline::cxe_finputline(char *filenametxt)
{
/* source right here is a pointer to an ifstream. Even though no
 * memory is allocated for the ifstream itself and it therefore can
 * have no information about what file to work with, the
 * is_open() status can be 1. This is because of what is interpreted
 * from the random noise that source points to. */

/* Now we need to give it some memory and fill it in with the file
 * name. This will call the ifstream constructor and make a memory
 * chunk big enough for it. By supplying the filename, the ifstream
 * will also be properly opened. */
if ( strcmp (filenametxt, "-") )
{
 source = new ifstream(filenametxt);
 /* Check for a bad filename or maybe some other problem. */
 if ( ! source->is_open() )
 { cout << "error opening file:" << filenametxt << "\n"; exit (1); }
}
else source = 0;
}
/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
cxe_finputline::~cxe_finputline()
{
/* This is the right place to close the file. */
if (source) { source->close();
/* Since source was filled with memory from new, we need delete. */
delete source; }
/* BTW if source is null, ignore it, cin closes itself magically */
}

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
char* cxe_finputline::readnextline()
{
/* Read a new line of text from the file - should be open */
if (source) source->getline(datatext, 500);
else cin.getline(datatext, 500);
/* New line just got read so word index is back to beginning of line. */
 wordindex = 0;
/* Pointer that points to the current word can point to the front. */
 wordposnow=datatext;
return datatext; }

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
bool cxe_finputline::eof()
{ 
if (source) return source->eof();
else return cin.eof();
}

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
bool cxe_finputline::is_open()
{ if (source) return source->is_open();
  else return 1; 
}
/* Hmm, should it return 1 for cin? I can't find a cin.is_open... */

/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
ostream &operator<<(ostream &os, cxe_finputline &P)
{ P.print(&os); return os; }
