summaryrefslogtreecommitdiff
path: root/common/richio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'common/richio.cpp')
-rw-r--r--common/richio.cpp607
1 files changed, 607 insertions, 0 deletions
diff --git a/common/richio.cpp b/common/richio.cpp
new file mode 100644
index 0000000..e0684d7
--- /dev/null
+++ b/common/richio.cpp
@@ -0,0 +1,607 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2007-2011 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
+ * Copyright (C) 2016 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+
+#include <cstdarg>
+
+#include <richio.h>
+
+
+// Fall back to getc() when getc_unlocked() is not available on the target platform.
+#if !defined( HAVE_FGETC_NOLOCK )
+#define getc_unlocked getc
+#endif
+
+
+static int vprint( std::string* result, const char* format, va_list ap )
+{
+ char msg[512];
+ // This function can call vsnprintf twice.
+ // But internally, vsnprintf retrieves arguments from the va_list identified by arg as if
+ // va_arg was used on it, and thus the state of the va_list is likely to be altered by the call.
+ // see: www.cplusplus.com/reference/cstdio/vsnprintf
+ // we make a copy of va_list ap for the second call, if happens
+ va_list tmp;
+ va_copy( tmp, ap );
+
+ size_t len = vsnprintf( msg, sizeof(msg), format, ap );
+
+ if( len < sizeof(msg) ) // the output fit into msg
+ {
+ result->append( msg, msg + len );
+ }
+ else
+ {
+ // output was too big, so now incur the expense of allocating
+ // a buf for holding suffient characters.
+
+ std::vector<char> buf;
+
+ buf.reserve( len+1 ); // reserve(), not resize() which writes. +1 for trailing nul.
+
+ len = vsnprintf( &buf[0], len+1, format, tmp );
+
+ result->append( &buf[0], &buf[0] + len );
+ }
+
+ va_end( tmp ); // Release the temporary va_list, initialised from ap
+
+ return len;
+}
+
+
+int StrPrintf( std::string* result, const char* format, ... )
+{
+ va_list args;
+
+ va_start( args, format );
+ int ret = vprint( result, format, args );
+ va_end( args );
+
+ return ret;
+}
+
+
+std::string StrPrintf( const char* format, ... )
+{
+ std::string ret;
+ va_list args;
+
+ va_start( args, format );
+ int ignore = vprint( &ret, format, args );
+ (void) ignore;
+ va_end( args );
+
+ return ret;
+}
+
+
+void IO_ERROR::init( const char* aThrowersFile, const char* aThrowersLoc, const wxString& aMsg )
+{
+ errorText.Printf( IO_FORMAT, aMsg.GetData(),
+ wxString::FromUTF8( aThrowersFile ).AfterLast( '/' ).GetData(),
+ wxString::FromUTF8( aThrowersLoc ).GetData() );
+}
+
+
+void PARSE_ERROR::init( const char* aThrowersFile, const char* aThrowersLoc,
+ const wxString& aMsg, const wxString& aSource,
+ const char* aInputLine,
+ int aLineNumber, int aByteIndex )
+{
+ // save inpuLine, lineNumber, and offset for UI (.e.g. Sweet text editor)
+ inputLine = aInputLine;
+ lineNumber = aLineNumber;
+ byteIndex = aByteIndex;
+
+ errorText.Printf( PARSE_FORMAT, aMsg.GetData(), aSource.GetData(),
+ aLineNumber, aByteIndex,
+ wxString::FromUTF8( aThrowersFile ).AfterLast( '/' ).GetData(),
+ wxString::FromUTF8( aThrowersLoc ).GetData() );
+}
+
+
+//-----<LINE_READER>------------------------------------------------------
+
+LINE_READER::LINE_READER( unsigned aMaxLineLength ) :
+ length( 0 ),
+ lineNum( 0 ),
+ line( NULL ),
+ capacity( 0 ),
+ maxLineLength( aMaxLineLength )
+{
+ if( aMaxLineLength != 0 )
+ {
+ // start at the INITIAL size, expand as needed up to the MAX size in maxLineLength
+ capacity = LINE_READER_LINE_INITIAL_SIZE;
+
+ // but never go above user's aMaxLineLength, and leave space for trailing nul
+ if( capacity > aMaxLineLength+1 )
+ capacity = aMaxLineLength+1;
+
+ line = new char[capacity];
+
+ line[0] = '\0';
+ }
+}
+
+
+LINE_READER::~LINE_READER()
+{
+ delete[] line;
+}
+
+
+void LINE_READER::expandCapacity( unsigned newsize )
+{
+ // length can equal maxLineLength and nothing breaks, there's room for
+ // the terminating nul. cannot go over this.
+ if( newsize > maxLineLength+1 )
+ newsize = maxLineLength+1;
+
+ if( newsize > capacity )
+ {
+ capacity = newsize;
+
+ // resize the buffer, and copy the original data
+ char* bigger = new char[capacity];
+
+ wxASSERT( capacity >= length+1 );
+
+ memcpy( bigger, line, length );
+ bigger[length] = 0;
+
+ delete[] line;
+ line = bigger;
+ }
+}
+
+
+FILE_LINE_READER::FILE_LINE_READER( const wxString& aFileName,
+ unsigned aStartingLineNumber,
+ unsigned aMaxLineLength ) throw( IO_ERROR ) :
+ LINE_READER( aMaxLineLength ),
+ iOwn( true )
+{
+ fp = wxFopen( aFileName, wxT( "rt" ) );
+ if( !fp )
+ {
+ wxString msg = wxString::Format(
+ _( "Unable to open filename '%s' for reading" ), aFileName.GetData() );
+ THROW_IO_ERROR( msg );
+ }
+
+ setvbuf( fp, NULL, _IOFBF, BUFSIZ * 8 );
+
+ source = aFileName;
+ lineNum = aStartingLineNumber;
+}
+
+
+FILE_LINE_READER::FILE_LINE_READER( FILE* aFile, const wxString& aFileName,
+ bool doOwn,
+ unsigned aStartingLineNumber,
+ unsigned aMaxLineLength ) :
+ LINE_READER( aMaxLineLength ),
+ iOwn( doOwn ),
+ fp( aFile )
+{
+ if( doOwn && ftell( aFile ) == 0L )
+ {
+#ifndef __WXMAC__
+ setvbuf( fp, NULL, _IOFBF, BUFSIZ * 8 );
+#endif
+ }
+ source = aFileName;
+ lineNum = aStartingLineNumber;
+}
+
+
+FILE_LINE_READER::~FILE_LINE_READER()
+{
+ if( iOwn && fp )
+ fclose( fp );
+}
+
+
+char* FILE_LINE_READER::ReadLine() throw( IO_ERROR )
+{
+ length = 0;
+
+ for(;;)
+ {
+ if( length >= maxLineLength )
+ THROW_IO_ERROR( _( "Maximum line length exceeded" ) );
+
+ if( length >= capacity )
+ expandCapacity( capacity * 2 );
+
+ // faster, POSIX compatible fgetc(), no locking.
+ int cc = getc_unlocked( fp );
+ if( cc == EOF )
+ break;
+
+ line[ length++ ] = (char) cc;
+
+ if( cc == '\n' )
+ break;
+ }
+
+ line[ length ] = 0;
+
+ // lineNum is incremented even if there was no line read, because this
+ // leads to better error reporting when we hit an end of file.
+ ++lineNum;
+
+ return length ? line : NULL;
+}
+
+
+STRING_LINE_READER::STRING_LINE_READER( const std::string& aString, const wxString& aSource ) :
+ LINE_READER( LINE_READER_LINE_DEFAULT_MAX ),
+ lines( aString ),
+ ndx( 0 )
+{
+ // Clipboard text should be nice and _use multiple lines_ so that
+ // we can report _line number_ oriented error messages when parsing.
+ source = aSource;
+}
+
+
+STRING_LINE_READER::STRING_LINE_READER( const STRING_LINE_READER& aStartingPoint ) :
+ LINE_READER( LINE_READER_LINE_DEFAULT_MAX ),
+ lines( aStartingPoint.lines ),
+ ndx( aStartingPoint.ndx )
+{
+ // since we are keeping the same "source" name, for error reporting purposes
+ // we need to have the same notion of line number and offset.
+
+ source = aStartingPoint.source;
+ lineNum = aStartingPoint.lineNum;
+}
+
+
+char* STRING_LINE_READER::ReadLine() throw( IO_ERROR )
+{
+ size_t nlOffset = lines.find( '\n', ndx );
+
+ if( nlOffset == std::string::npos )
+ length = lines.length() - ndx;
+ else
+ length = nlOffset - ndx + 1; // include the newline, so +1
+
+ if( length )
+ {
+ if( length >= maxLineLength )
+ THROW_IO_ERROR( _("Line length exceeded") );
+
+ if( length+1 > capacity ) // +1 for terminating nul
+ expandCapacity( length+1 );
+
+ wxASSERT( ndx + length <= lines.length() );
+
+ memcpy( line, &lines[ndx], length );
+
+ ndx += length;
+ }
+
+ ++lineNum; // this gets incremented even if no bytes were read
+
+ line[length] = 0;
+
+ return length ? line : NULL;
+}
+
+
+INPUTSTREAM_LINE_READER::INPUTSTREAM_LINE_READER( wxInputStream* aStream, const wxString& aSource ) :
+ LINE_READER( LINE_READER_LINE_DEFAULT_MAX ),
+ m_stream( aStream )
+{
+ source = aSource;
+}
+
+
+char* INPUTSTREAM_LINE_READER::ReadLine() throw( IO_ERROR )
+{
+ length = 0;
+
+ for(;;)
+ {
+ if( length >= maxLineLength )
+ THROW_IO_ERROR( _( "Maximum line length exceeded" ) );
+
+ if( length + 1 > capacity )
+ expandCapacity( capacity * 2 );
+
+ // this read may fail, docs say to test LastRead() before trusting cc.
+ char cc = m_stream->GetC();
+
+ if( !m_stream->LastRead() )
+ break;
+
+ line[ length++ ] = cc;
+
+ if( cc == '\n' )
+ break;
+ }
+
+ line[ length ] = 0;
+
+ // lineNum is incremented even if there was no line read, because this
+ // leads to better error reporting when we hit an end of file.
+ ++lineNum;
+
+ return length ? line : NULL;
+}
+
+
+//-----<OUTPUTFORMATTER>----------------------------------------------------
+
+// factor out a common GetQuoteChar
+
+const char* OUTPUTFORMATTER::GetQuoteChar( const char* wrapee, const char* quote_char )
+{
+ // Include '#' so a symbol is not confused with a comment. We intend
+ // to wrap any symbol starting with a '#'.
+ // Our LEXER class handles comments, and comments appear to be an extension
+ // to the SPECCTRA DSN specification.
+ if( *wrapee == '#' )
+ return quote_char;
+
+ if( strlen( wrapee ) == 0 )
+ return quote_char;
+
+ bool isFirst = true;
+
+ for( ; *wrapee; ++wrapee, isFirst = false )
+ {
+ static const char quoteThese[] = "\t ()"
+ "%" // per Alfons of freerouting.net, he does not like this unquoted as of 1-Feb-2008
+ "{}" // guessing that these are problems too
+ ;
+
+ // if the string to be wrapped (wrapee) has a delimiter in it,
+ // return the quote_char so caller wraps the wrapee.
+ if( strchr( quoteThese, *wrapee ) )
+ return quote_char;
+
+ if( !isFirst && '-' == *wrapee )
+ return quote_char;
+ }
+
+ return ""; // caller does not need to wrap, can use an unwrapped string.
+}
+
+
+const char* OUTPUTFORMATTER::GetQuoteChar( const char* wrapee )
+{
+ return GetQuoteChar( wrapee, quoteChar );
+}
+
+int OUTPUTFORMATTER::vprint( const char* fmt, va_list ap ) throw( IO_ERROR )
+{
+ // This function can call vsnprintf twice.
+ // But internally, vsnprintf retrieves arguments from the va_list identified by arg as if
+ // va_arg was used on it, and thus the state of the va_list is likely to be altered by the call.
+ // see: www.cplusplus.com/reference/cstdio/vsnprintf
+ // we make a copy of va_list ap for the second call, if happens
+ va_list tmp;
+ va_copy( tmp, ap );
+ int ret = vsnprintf( &buffer[0], buffer.size(), fmt, ap );
+
+ if( ret >= (int) buffer.size() )
+ {
+ buffer.resize( ret + 1000 );
+ ret = vsnprintf( &buffer[0], buffer.size(), fmt, tmp );
+ }
+
+ va_end( tmp ); // Release the temporary va_list, initialised from ap
+
+ if( ret > 0 )
+ write( &buffer[0], ret );
+
+ return ret;
+}
+
+
+int OUTPUTFORMATTER::sprint( const char* fmt, ... ) throw( IO_ERROR )
+{
+ va_list args;
+
+ va_start( args, fmt );
+ int ret = vprint( fmt, args);
+ va_end( args );
+
+ return ret;
+}
+
+
+int OUTPUTFORMATTER::Print( int nestLevel, const char* fmt, ... ) throw( IO_ERROR )
+{
+#define NESTWIDTH 2 ///< how many spaces per nestLevel
+
+ va_list args;
+
+ va_start( args, fmt );
+
+ int result = 0;
+ int total = 0;
+
+ for( int i=0; i<nestLevel; ++i )
+ {
+ // no error checking needed, an exception indicates an error.
+ result = sprint( "%*c", NESTWIDTH, ' ' );
+
+ total += result;
+ }
+
+ // no error checking needed, an exception indicates an error.
+ result = vprint( fmt, args );
+
+ va_end( args );
+
+ total += result;
+ return total;
+}
+
+
+std::string OUTPUTFORMATTER::Quotes( const std::string& aWrapee ) throw( IO_ERROR )
+{
+ static const char quoteThese[] = "\t ()\n\r";
+
+ if( !aWrapee.size() || // quote null string as ""
+ aWrapee[0]=='#' || // quote a potential s-expression comment, so it is not a comment
+ aWrapee[0]=='"' || // NextTok() will travel through DSN_STRING path anyway, then must apply escapes
+ aWrapee.find_first_of( quoteThese ) != std::string::npos )
+ {
+ std::string ret;
+
+ ret.reserve( aWrapee.size()*2 + 2 );
+
+ ret += '"';
+
+ for( std::string::const_iterator it = aWrapee.begin(); it!=aWrapee.end(); ++it )
+ {
+ switch( *it )
+ {
+ case '\n':
+ ret += '\\';
+ ret += 'n';
+ break;
+ case '\r':
+ ret += '\\';
+ ret += 'r';
+ break;
+ case '\\':
+ ret += '\\';
+ ret += '\\';
+ break;
+ case '"':
+ ret += '\\';
+ ret += '"';
+ break;
+ default:
+ ret += *it;
+ }
+ }
+
+ ret += '"';
+
+ return ret;
+ }
+
+ return aWrapee;
+}
+
+
+std::string OUTPUTFORMATTER::Quotew( const wxString& aWrapee ) throw( IO_ERROR )
+{
+ // wxStrings are always encoded as UTF-8 as we convert to a byte sequence.
+ // The non-virutal function calls the virtual workhorse function, and if
+ // a different quoting or escaping strategy is desired from the standard,
+ // a derived class can overload Quotes() above, but
+ // should never be a reason to overload this Quotew() here.
+ return Quotes( (const char*) aWrapee.utf8_str() );
+}
+
+
+//-----<STRING_FORMATTER>----------------------------------------------------
+
+void STRING_FORMATTER::write( const char* aOutBuf, int aCount ) throw( IO_ERROR )
+{
+ mystring.append( aOutBuf, aCount );
+}
+
+void STRING_FORMATTER::StripUseless()
+{
+ std::string copy = mystring;
+
+ mystring.clear();
+
+ for( std::string::iterator i=copy.begin(); i!=copy.end(); ++i )
+ {
+ if( !isspace( *i ) && *i!=')' && *i!='(' && *i!='"' )
+ {
+ mystring += *i;
+ }
+ }
+}
+
+//-----<FILE_OUTPUTFORMATTER>----------------------------------------
+
+FILE_OUTPUTFORMATTER::FILE_OUTPUTFORMATTER( const wxString& aFileName,
+ const wxChar* aMode, char aQuoteChar ) throw( IO_ERROR ) :
+ OUTPUTFORMATTER( OUTPUTFMTBUFZ, aQuoteChar ),
+ m_filename( aFileName )
+{
+ m_fp = wxFopen( aFileName, aMode );
+
+ if( !m_fp )
+ {
+ wxString msg = wxString::Format(
+ _( "cannot open or save file '%s'" ),
+ m_filename.GetData() );
+ THROW_IO_ERROR( msg );
+ }
+}
+
+
+FILE_OUTPUTFORMATTER::~FILE_OUTPUTFORMATTER()
+{
+ if( m_fp )
+ fclose( m_fp );
+}
+
+
+void FILE_OUTPUTFORMATTER::write( const char* aOutBuf, int aCount ) throw( IO_ERROR )
+{
+ if( 1 != fwrite( aOutBuf, aCount, 1, m_fp ) )
+ {
+ wxString msg = wxString::Format(
+ _( "error writing to file '%s'" ),
+ m_filename.GetData() );
+ THROW_IO_ERROR( msg );
+ }
+}
+
+
+//-----<STREAM_OUTPUTFORMATTER>--------------------------------------
+
+void STREAM_OUTPUTFORMATTER::write( const char* aOutBuf, int aCount ) throw( IO_ERROR )
+{
+ int lastWrite;
+
+ // This might delay awhile if you were writing to say a socket, but for
+ // a file it should only go through the loop once.
+ for( int total = 0; total<aCount; total += lastWrite )
+ {
+ lastWrite = os.Write( aOutBuf, aCount ).LastWrite();
+
+ if( !os.IsOk() )
+ {
+ THROW_IO_ERROR( _( "OUTPUTSTREAM_OUTPUTFORMATTER write error" ) );
+ }
+ }
+}
+