/* Copyright (C) 2010 Andrew Church This file is part of Aquaria. Aquaria is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once /* * This class implements a lightweight version of the std::istringstream * class used for parsing strings. std::istringstream (like many STL * classes) requires a significant amount of overhead to implement its * flexibility, and in some environments, the simple act of reading a * floating-point value: * * std::istringstream is; float f; is >> f; * * can be 20-30 times (not a typo!) slower than the C equivalent: * * float f = strtof(s, &s); * * This class eschews most of std::istringstream's flexibility in favor of * a more efficient implementation to improve performance when reading * text-format data files. * * To create a new SimpleIStringStream, initialize it with or assign to it * the string to be parsed: * * SimpleIStringStream is("123 4.5 -6.78e9"); // Alternative 1 * SimpleIStringStream is = "123 4.5 -6.78e9"; // Alternative 2 * * If the string is a "char *" or "const char *" that is guaranteed to * exist throughout the lifetime of the SimpleIStringStream instance, you * can also use this more efficient constructor: * * SimpleIStringStream is("123 4.5 -6.78e9", SimpleIStringStream::REUSE); * * which avoids making a separate copy of the string buffer. (It is then * incumbent upon the caller to ensure that the string actually remains * valid throughout the SimpleIStringStream's lifetime.) Alternatively, if * you have a dynamically-allocated "char *" buffer that you want the * SimpleIStringStream to free for you when you're done with it, construct * the object like this: * * SimpleIStringStream is(buffer, SimpleIStringStream::TAKE_OVER); * * In either of the latter two cases, you must not modify the string buffer * while the SimpleIStringStream object exists. * * While it is possible to create a SimpleIStringStream using a std::string * object, the object's contents will always be copied on creation for * performance reasons; it is not possible to use the REUSE or TAKE_OVER * flags with STL strings. * * The only operators (other than assignment) implemented by * SimpleIStringStream are "bool()", for testing end-of-stream (in the same * manner as std::istringstream), and ">>", the extraction operator. All * arithmetic, std::string, and character extractors supported by * std::istringstream except "long double" are also supported here, but not * the C string or function extractors. */ #include "Base.h" /*************************************************************************/ /** * SISS_VERIFY: Verify the behavior of SimpleIStringStream by creating an * istringstream alongside the local buffer and ensuring that it always * extracts the same values returned by the SimpleIStringStream. */ // #define SISS_VERIFY /*************************************************************************/ /*************************** Class definition ****************************/ /*************************************************************************/ class SimpleIStringStream { public: /* Reuse flag passed to StringStream(char *,int). */ enum { /* Make a copy of the buffer (default action). */ COPY, /* Use the passed-in string pointer as is. Requires the string * pointer to remain valid over the life of this object. */ REUSE, /* Take over the passed-in string buffer, which must have been * allocated with new[]. The buffer will be deleted on object * destruction. */ TAKE_OVER, }; /*-------------------------------------------------------------------*/ /** * SimpleIStringStream(): Basic object constructor. No extraction is * possible until a string has been assigned. */ inline SimpleIStringStream(); /** * SimpleIStringStream(...): Create a new SimpleIStringStream from a * C-style or STL string. Each of these constructors is exactly * equivalent to creating a SimpleIString with the default constructor * and then calling setString() with the same parameters. */ inline SimpleIStringStream(char *string, int reuse_flag = COPY); inline SimpleIStringStream(const char *string, int reuse_flag = COPY); inline SimpleIStringStream(std::string string); /** * ~SimpleIStringStream(): Object destructor. */ inline ~SimpleIStringStream(); /*------------------------------*/ /** * setString(...): Set the string from which data will be extracted, * replacing any existing string. * * [Parameters] * string: String to use for extraction. * reuse_flag: Flag indicating buffer reuse semantics for "char *" * string buffers. * [Return value] * None */ inline void setString(char *string, int reuse_flag = COPY); inline void setString(const char *string, int reuse_flag = COPY); inline void setString(const std::string &string); /** * operator=(char *), operator=(const char *), operator=(std::string): * Set the string from which data will be extracted. Exactly * equivalent to setString(string). * * [Parameters] * string: Assignment source * [Return value] * this */ inline SimpleIStringStream &operator=(char *string); inline SimpleIStringStream &operator=(const char *string); inline SimpleIStringStream &operator=(const std::string &string); /** * operator=(SimpleIStringStream): Make a copy of an existing stream. * The input string buffer (if any) will always be copied, regardless * of any reuse_flag passed to the original stream. * * [Parameters] * stream: Assignment source * [Return value] * this */ inline SimpleIStringStream &operator=(const SimpleIStringStream &stream); /*------------------------------*/ /** * operator bool: Evaluate the stream as a boolean. Returns false if * an error has occurred, true otherwise. (This allows the stream to * be used in a while loop like "while (stream >> var)", in the same * way as ordinary std::istringstream objects.) * * [Parameters] * None * [Return value] * False if an error has occurred, else true */ inline operator bool() const; /** * operator>>: Extract a value from a stream. String extraction skips * leading whitespace and stops at the first following whitespace * character. * * [Parameters] * target: Target for storing extracted value (right-hand operand) * [Return value] * this */ inline SimpleIStringStream &operator>>(bool &target); inline SimpleIStringStream &operator>>(short &target); inline SimpleIStringStream &operator>>(unsigned short &target); inline SimpleIStringStream &operator>>(int &target); inline SimpleIStringStream &operator>>(unsigned int &target); inline SimpleIStringStream &operator>>(long &target); inline SimpleIStringStream &operator>>(unsigned long &target); inline SimpleIStringStream &operator>>(float &target); inline SimpleIStringStream &operator>>(double &target); inline SimpleIStringStream &operator>>(char &target); inline SimpleIStringStream &operator>>(signed char &target); inline SimpleIStringStream &operator>>(unsigned char &target); inline SimpleIStringStream &operator>>(std::string &target); /*-------------------------------------------------------------------*/ private: char *buffer; // The buffer we're parsing. char *position; // Our current position in the buffer. bool freeOnDestroy; // Should we free the buffer when we're destroyed? bool error; // Current error status. #ifdef SISS_VERIFY std::istringstream std_is; unsigned long ispos; #endif /* Is the given character a whitespace character? */ inline bool my_isspace(char c) const { return c==' ' || c=='\t' || c=='\r' || c=='\n' || c=='\v'; }; /* Skip over leading whitespace. Assumes "position" is valid. */ inline void skip_spaces() { while (my_isspace(*position)) { position++; } } }; // class SimpleIStringStream /*************************************************************************/ /*********************** Inline method definitions ***********************/ /*************************************************************************/ /* Helper macros used with SISS_VERIFY to verify result values. "type" is * the type of data being read, and "def_value" is the default value to * test against when the stream reports an error. */ #ifdef SISS_VERIFY extern void debugLog(const std::string &s); #define VERIFY_SETUP \ char *old_position = position; #define VERIFY(type,def_value) do { \ type test; \ std_is >> test; \ ispos += std_is.gcount(); \ if (std_is.fail()) { \ test = def_value; \ } \ if (test != target) { \ std::ostringstream os_target; os_target << target; \ std::ostringstream os_test; os_test << test; \ std::ostringstream os_offset; os_offset << (position - buffer); \ debugLog(std::string("SimpleIStringStream >> " #type ": MISMATCH") \ + " (us=" + os_target.str() \ + " STL=" + (std_is.fail() ? "<>" : os_test.str()) \ + ") at: [" + std::string(old_position).substr(0,100) \ + "] ([" + std::string(buffer).substr(0,100) + "] + " \ + os_offset.str() + ")"); \ } \ } while (0) #else #define VERIFY_SETUP /*nothing*/ #define VERIFY(type,def_value) /*nothing*/ #endif /*************************************************************************/ inline SimpleIStringStream::SimpleIStringStream() { buffer = NULL; position = NULL; freeOnDestroy = false; error = false; } inline SimpleIStringStream::SimpleIStringStream(char *string, int reuse_flag) { freeOnDestroy = false; // Don't try to free the uninitialized pointer. setString(string, reuse_flag); } inline SimpleIStringStream::SimpleIStringStream(const char *string, int reuse_flag) { freeOnDestroy = false; setString(string, reuse_flag); } inline SimpleIStringStream::SimpleIStringStream(std::string string) { freeOnDestroy = false; setString(string); } inline SimpleIStringStream::~SimpleIStringStream() { if (freeOnDestroy) { delete[] buffer; } } /*-----------------------------------------------------------------------*/ inline void SimpleIStringStream::setString(char *string, int reuse_flag) { if (freeOnDestroy) { delete[] buffer; } if (reuse_flag == REUSE) { buffer = string; freeOnDestroy = false; } else if (reuse_flag == TAKE_OVER) { buffer = string; freeOnDestroy = true; } else { const size_t size = strlen(string) + 1; buffer = new char[size]; freeOnDestroy = true; if (buffer) { memcpy(buffer, string, size); } } position = buffer; error = false; #ifdef SISS_VERIFY if (buffer) { std_is.str(buffer); std_is.clear(); ispos = 0; } #endif } inline void SimpleIStringStream::setString(const char *string, int reuse_flag) { if (freeOnDestroy) { delete[] buffer; } if (reuse_flag == REUSE) { /* We never actually write to the buffer, and we won't attempt to * free it, so this cast is safe. */ buffer = const_cast(string); freeOnDestroy = false; } else { /* It makes no sense to TAKE_OVER a const char *, since we could * never free it, so we treat TAKE_OVER like COPY. */ const size_t size = strlen(string) + 1; buffer = new char[size]; freeOnDestroy = true; if (buffer) { memcpy(buffer, string, size); } } position = buffer; error = false; #ifdef SISS_VERIFY if (buffer) { std_is.str(buffer); std_is.clear(); ispos = 0; } #endif } inline void SimpleIStringStream::setString(const std::string &string) { setString(string.c_str(), COPY); } inline SimpleIStringStream &SimpleIStringStream::operator=(char *string) { setString(string); return *this; } inline SimpleIStringStream &SimpleIStringStream::operator=(const char *string) { setString(string); return *this; } inline SimpleIStringStream &SimpleIStringStream::operator=( const std::string &string) { setString(string); return *this; } inline SimpleIStringStream &SimpleIStringStream::operator=( const SimpleIStringStream &stream) { /* Watch out that we don't try to assign ourselves, or we'll raise a * use-after-free bug. */ if (&stream == this) { return *this; } setString(stream.buffer, COPY); return *this; } /*-----------------------------------------------------------------------*/ inline SimpleIStringStream::operator bool() const { #ifdef SISS_VERIFY if (!error != bool(std_is)) { std::ostringstream os_offset; os_offset << (position - buffer); debugLog(std::string("SimpleIStringStream bool MISMATCH: us=") + (!error ? "true" : "false") + " STL=" + (std_is ? "true" : "false") + " at: [" + std::string(position).substr(0,100) + "] ([" + std::string(buffer).substr(0,100) + "] + " + os_offset.str() + ")"); } #endif return !error; } /*-----------------------------------------------------------------------*/ inline SimpleIStringStream &SimpleIStringStream::operator>>(bool &target) { if (position) { char *old_position = position; if (error) { target = false; } else { skip_spaces(); unsigned long longval = strtoul(position, &position, 10); target = (longval == 1); error = (position == old_position || *position == 0); } VERIFY(bool, false); } else { target = false; error = true; } return *this; } /*----------------------------------*/ inline SimpleIStringStream &SimpleIStringStream::operator>>(short &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = (short)strtol(position, &position, 0); error = (position == old_position || *position == 0); } VERIFY(short, 0); } else { target = 0; error = true; } return *this; } inline SimpleIStringStream &SimpleIStringStream::operator>>(unsigned short &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = (unsigned short)strtoul(position, &position, 0); error = (position == old_position || *position == 0); } VERIFY(unsigned short, 0); } else { target = 0; error = true; } return *this; } /*----------------------------------*/ inline SimpleIStringStream &SimpleIStringStream::operator>>(int &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = (int)strtol(position, &position, 0); error = (position == old_position || *position == 0); } VERIFY(int, 0); } else { target = 0; error = true; } return *this; } inline SimpleIStringStream &SimpleIStringStream::operator>>(unsigned int &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = (unsigned int)strtoul(position, &position, 0); error = (position == old_position || *position == 0); } VERIFY(unsigned int, 0); } else { target = 0; error = true; } return *this; } /*----------------------------------*/ inline SimpleIStringStream &SimpleIStringStream::operator>>(long &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = strtol(position, &position, 0); error = (position == old_position || *position == 0); } VERIFY(long, 0); } else { target = 0; error = true; } return *this; } inline SimpleIStringStream &SimpleIStringStream::operator>>(unsigned long &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = strtoul(position, &position, 0); error = (position == old_position || *position == 0); } VERIFY(unsigned long, 0); } else { target = 0; error = true; } return *this; } /*----------------------------------*/ inline SimpleIStringStream &SimpleIStringStream::operator>>(float &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = strtof(position, &position); error = (position == old_position || *position == 0); } VERIFY(float, 0); } else { target = 0; error = true; } return *this; } inline SimpleIStringStream &SimpleIStringStream::operator>>(double &target) { if (position) { char *old_position = position; if (error) { target = 0; } else { skip_spaces(); target = strtod(position, &position); error = (position == old_position || *position == 0); } VERIFY(double, 0); } else { target = 0; error = true; } return *this; } /*----------------------------------*/ inline SimpleIStringStream &SimpleIStringStream::operator>>(char &target) { if (position) { #ifdef SISS_VERIFY char *old_position = position; #endif if (error) { target = 0; } else { target = *position; if (*position) { position++; error = (*position == 0); } else { error = true; } } VERIFY(char, 0); } else { target = 0; error = true; } return *this; } inline SimpleIStringStream &SimpleIStringStream::operator>>(signed char &target) { if (position) { #ifdef SISS_VERIFY char *old_position = position; #endif if (error) { target = 0; } else { target = *position; if (*position) { position++; error = (*position == 0); } else { error = true; } } VERIFY(signed char, 0); } else { target = 0; error = true; } return *this; } inline SimpleIStringStream &SimpleIStringStream::operator>>(unsigned char &target) { if (position) { #ifdef SISS_VERIFY char *old_position = position; #endif if (error) { target = 0; } else { target = *position; if (*position) { position++; error = (*position == 0); } else { error = true; } } VERIFY(unsigned char, 0); } else { target = 0; error = true; } return *this; } /*----------------------------------*/ inline SimpleIStringStream &SimpleIStringStream::operator>>(std::string &target) { if (position) { #ifdef SISS_VERIFY char *old_position = position; #endif if (error) { target = ""; } else { skip_spaces(); if (!*position) { target = ""; error = true; } else { char *start = position; while (*position && !my_isspace(*position)) { position++; } target.assign(start, position - start); error = (*position == 0); } } VERIFY(std::string, ""); } else { target = ""; error = true; } return *this; } /*************************************************************************/ #undef VERIFY_SETUP #undef VERIFY /*************************************************************************/ /*************************************************************************/ /* * Local variables: * indent-tabs-mode: t * tab-width: 4 * End: * * vim: shiftwidth=4: */