880 lines
22 KiB
C++
880 lines
22 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2007-2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
|
|
* Copyright (C) 2007-2024 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 <charconv>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib> // bsearch()
|
|
#include <cctype>
|
|
|
|
#include <dsnlexer.h>
|
|
#include <wx/translation.h>
|
|
|
|
#define FMT_CLIPBOARD _( "clipboard" )
|
|
|
|
|
|
//-----<DSNLEXER>-------------------------------------------------------------
|
|
|
|
void DSNLEXER::init()
|
|
{
|
|
curTok = DSN_NONE;
|
|
prevTok = DSN_NONE;
|
|
|
|
stringDelimiter = '"';
|
|
|
|
specctraMode = false;
|
|
space_in_quoted_tokens = false;
|
|
commentsAreTokens = false;
|
|
|
|
curOffset = 0;
|
|
}
|
|
|
|
|
|
DSNLEXER::DSNLEXER( const KEYWORD* aKeywordTable, unsigned aKeywordCount,
|
|
const KEYWORD_MAP* aKeywordMap,
|
|
FILE* aFile, const wxString& aFilename ) :
|
|
iOwnReaders( true ),
|
|
start( nullptr ),
|
|
next( nullptr ),
|
|
limit( nullptr ),
|
|
reader( nullptr ),
|
|
keywords( aKeywordTable ),
|
|
keywordCount( aKeywordCount ),
|
|
keywordsLookup( aKeywordMap )
|
|
{
|
|
FILE_LINE_READER* fileReader = new FILE_LINE_READER( aFile, aFilename );
|
|
PushReader( fileReader );
|
|
init();
|
|
}
|
|
|
|
|
|
DSNLEXER::DSNLEXER( const KEYWORD* aKeywordTable, unsigned aKeywordCount,
|
|
const KEYWORD_MAP* aKeywordMap,
|
|
const std::string& aClipboardTxt, const wxString& aSource ) :
|
|
iOwnReaders( true ),
|
|
start( nullptr ),
|
|
next( nullptr ),
|
|
limit( nullptr ),
|
|
reader( nullptr ),
|
|
keywords( aKeywordTable ),
|
|
keywordCount( aKeywordCount ),
|
|
keywordsLookup( aKeywordMap )
|
|
{
|
|
STRING_LINE_READER* stringReader = new STRING_LINE_READER( aClipboardTxt, aSource.IsEmpty() ?
|
|
wxString( FMT_CLIPBOARD ) : aSource );
|
|
PushReader( stringReader );
|
|
init();
|
|
}
|
|
|
|
|
|
DSNLEXER::DSNLEXER( const KEYWORD* aKeywordTable, unsigned aKeywordCount,
|
|
const KEYWORD_MAP* aKeywordMap,
|
|
LINE_READER* aLineReader ) :
|
|
iOwnReaders( false ),
|
|
start( nullptr ),
|
|
next( nullptr ),
|
|
limit( nullptr ),
|
|
reader( nullptr ),
|
|
keywords( aKeywordTable ),
|
|
keywordCount( aKeywordCount ),
|
|
keywordsLookup( aKeywordMap )
|
|
{
|
|
if( aLineReader )
|
|
PushReader( aLineReader );
|
|
init();
|
|
}
|
|
|
|
|
|
static const KEYWORD empty_keywords[1] = {};
|
|
|
|
DSNLEXER::DSNLEXER( const std::string& aSExpression, const wxString& aSource ) :
|
|
iOwnReaders( true ),
|
|
start( nullptr ),
|
|
next( nullptr ),
|
|
limit( nullptr ),
|
|
reader( nullptr ),
|
|
keywords( empty_keywords ),
|
|
keywordCount( 0 ),
|
|
keywordsLookup( nullptr )
|
|
{
|
|
STRING_LINE_READER* stringReader = new STRING_LINE_READER( aSExpression, aSource.IsEmpty() ?
|
|
wxString( FMT_CLIPBOARD ) : aSource );
|
|
PushReader( stringReader );
|
|
init();
|
|
}
|
|
|
|
|
|
DSNLEXER::~DSNLEXER()
|
|
{
|
|
if( iOwnReaders )
|
|
{
|
|
// delete the LINE_READERs from the stack, since I own them.
|
|
for( READER_STACK::iterator it = readerStack.begin(); it!=readerStack.end(); ++it )
|
|
delete *it;
|
|
}
|
|
}
|
|
|
|
|
|
void DSNLEXER::SetSpecctraMode( bool aMode )
|
|
{
|
|
specctraMode = aMode;
|
|
if( aMode )
|
|
{
|
|
// specctra mode defaults, some of which can still be changed in this mode.
|
|
space_in_quoted_tokens = true;
|
|
}
|
|
else
|
|
{
|
|
space_in_quoted_tokens = false;
|
|
stringDelimiter = '"';
|
|
}
|
|
}
|
|
|
|
|
|
void DSNLEXER::InitParserState()
|
|
{
|
|
curTok = DSN_NONE;
|
|
prevTok = DSN_NONE;
|
|
commentsAreTokens = false;
|
|
|
|
curOffset = 0;
|
|
}
|
|
|
|
|
|
bool DSNLEXER::SyncLineReaderWith( DSNLEXER& aLexer )
|
|
{
|
|
// Synchronize the pointers handling the data read by the LINE_READER
|
|
// only if aLexer shares the same LINE_READER, because only in this case
|
|
// the char buffer is be common
|
|
|
|
if( reader != aLexer.reader )
|
|
return false;
|
|
|
|
// We can synchronize the pointers which handle the data currently read
|
|
start = aLexer.start;
|
|
next = aLexer.next;
|
|
limit = aLexer.limit;
|
|
|
|
// Sync these parameters is not mandatory, but could help
|
|
// for instance in debug
|
|
curText = aLexer.curText;
|
|
curOffset = aLexer.curOffset;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void DSNLEXER::PushReader( LINE_READER* aLineReader )
|
|
{
|
|
readerStack.push_back( aLineReader );
|
|
reader = aLineReader;
|
|
start = (const char*) (*reader);
|
|
|
|
// force a new readLine() as first thing.
|
|
limit = start;
|
|
next = start;
|
|
}
|
|
|
|
|
|
LINE_READER* DSNLEXER::PopReader()
|
|
{
|
|
LINE_READER* ret = nullptr;
|
|
|
|
if( readerStack.size() )
|
|
{
|
|
ret = reader;
|
|
readerStack.pop_back();
|
|
|
|
if( readerStack.size() )
|
|
{
|
|
reader = readerStack.back();
|
|
start = reader->Line();
|
|
|
|
// force a new readLine() as first thing.
|
|
limit = start;
|
|
next = start;
|
|
}
|
|
else
|
|
{
|
|
reader = nullptr;
|
|
start = dummy;
|
|
limit = dummy;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
int DSNLEXER::findToken( const std::string& tok ) const
|
|
{
|
|
if( keywordsLookup != nullptr )
|
|
{
|
|
KEYWORD_MAP::const_iterator it = keywordsLookup->find( tok.c_str() );
|
|
|
|
if( it != keywordsLookup->end() )
|
|
return it->second;
|
|
}
|
|
|
|
return DSN_SYMBOL; // not a keyword, some arbitrary symbol.
|
|
}
|
|
|
|
|
|
const char* DSNLEXER::Syntax( int aTok )
|
|
{
|
|
const char* ret;
|
|
|
|
switch( aTok )
|
|
{
|
|
case DSN_NONE:
|
|
ret = "NONE";
|
|
break;
|
|
case DSN_STRING_QUOTE:
|
|
ret = "string_quote"; // a special DSN syntax token, see specctra spec.
|
|
break;
|
|
case DSN_QUOTE_DEF:
|
|
ret = "quoted text delimiter";
|
|
break;
|
|
case DSN_DASH:
|
|
ret = "-";
|
|
break;
|
|
case DSN_SYMBOL:
|
|
ret = "symbol";
|
|
break;
|
|
case DSN_NUMBER:
|
|
ret = "number";
|
|
break;
|
|
case DSN_RIGHT:
|
|
ret = ")";
|
|
break;
|
|
case DSN_LEFT:
|
|
ret = "(";
|
|
break;
|
|
case DSN_STRING:
|
|
ret = "quoted string";
|
|
break;
|
|
case DSN_EOF:
|
|
ret = "end of input";
|
|
break;
|
|
default:
|
|
ret = "???";
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
const char* DSNLEXER::GetTokenText( int aTok ) const
|
|
{
|
|
const char* ret;
|
|
|
|
if( aTok < 0 )
|
|
{
|
|
return Syntax( aTok );
|
|
}
|
|
else if( (unsigned) aTok < keywordCount )
|
|
{
|
|
ret = keywords[aTok].name;
|
|
}
|
|
else
|
|
ret = "token too big";
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
wxString DSNLEXER::GetTokenString( int aTok ) const
|
|
{
|
|
wxString ret;
|
|
|
|
ret << wxT("'") << wxString::FromUTF8( GetTokenText(aTok) ) << wxT("'");
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool DSNLEXER::IsSymbol( int aTok )
|
|
{
|
|
// This is static and not inline to reduce code space.
|
|
|
|
// if aTok is >= 0, then it is a coincidental match to a keyword.
|
|
return aTok == DSN_SYMBOL || aTok == DSN_STRING || aTok >= 0;
|
|
}
|
|
|
|
|
|
void DSNLEXER::Expecting( int aTok ) const
|
|
{
|
|
wxString errText = wxString::Format(
|
|
_( "Expecting %s" ), GetTokenString( aTok ) );
|
|
THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
|
|
void DSNLEXER::Expecting( const char* text ) const
|
|
{
|
|
wxString errText = wxString::Format(
|
|
_( "Expecting '%s'" ), wxString::FromUTF8( text ) );
|
|
THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
|
|
void DSNLEXER::Unexpected( int aTok ) const
|
|
{
|
|
wxString errText = wxString::Format(
|
|
_( "Unexpected %s" ), GetTokenString( aTok ) );
|
|
THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
|
|
void DSNLEXER::Duplicate( int aTok )
|
|
{
|
|
wxString errText = wxString::Format(
|
|
_("%s is a duplicate"), GetTokenString( aTok ).GetData() );
|
|
THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
|
|
void DSNLEXER::Unexpected( const char* text ) const
|
|
{
|
|
wxString errText = wxString::Format(
|
|
_( "Unexpected '%s'" ), wxString::FromUTF8( text ) );
|
|
THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
|
|
void DSNLEXER::NeedLEFT()
|
|
{
|
|
int tok = NextTok();
|
|
if( tok != DSN_LEFT )
|
|
Expecting( DSN_LEFT );
|
|
}
|
|
|
|
|
|
void DSNLEXER::NeedRIGHT()
|
|
{
|
|
int tok = NextTok();
|
|
|
|
if( tok != DSN_RIGHT )
|
|
Expecting( DSN_RIGHT );
|
|
}
|
|
|
|
|
|
int DSNLEXER::NeedSYMBOL()
|
|
{
|
|
int tok = NextTok();
|
|
|
|
if( !IsSymbol( tok ) )
|
|
Expecting( DSN_SYMBOL );
|
|
|
|
return tok;
|
|
}
|
|
|
|
|
|
int DSNLEXER::NeedSYMBOLorNUMBER()
|
|
{
|
|
int tok = NextTok();
|
|
|
|
if( !IsSymbol( tok ) && tok!=DSN_NUMBER )
|
|
Expecting( "a symbol or number" );
|
|
|
|
return tok;
|
|
}
|
|
|
|
|
|
int DSNLEXER::NeedNUMBER( const char* aExpectation )
|
|
{
|
|
int tok = NextTok();
|
|
|
|
if( tok != DSN_NUMBER )
|
|
{
|
|
wxString errText = wxString::Format( _( "need a number for '%s'" ),
|
|
wxString::FromUTF8( aExpectation ).GetData() );
|
|
THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
return tok;
|
|
}
|
|
|
|
|
|
/**
|
|
* Test for whitespace.
|
|
*
|
|
* Our whitespace, by our definition, is a subset of ASCII, i.e. no bytes with MSB on can be
|
|
* considered whitespace, since they are likely part of a multibyte UTF8 character.
|
|
*/
|
|
static bool isSpace( char cc )
|
|
{
|
|
// cc is signed, so it is often negative.
|
|
// Treat negative as large positive to exclude rapidly.
|
|
if( (unsigned char) cc <= ' ' )
|
|
{
|
|
switch( (unsigned char) cc )
|
|
{
|
|
case ' ':
|
|
case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
case '\0': // PCAD s-expression files have this.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
inline bool isDigit( char cc )
|
|
{
|
|
return '0' <= cc && cc <= '9';
|
|
}
|
|
|
|
|
|
///< @return true if @a cc is an s-expression separator character.
|
|
inline bool isSep( char cc )
|
|
{
|
|
return isSpace( cc ) || cc=='(' || cc==')';
|
|
}
|
|
|
|
|
|
/**
|
|
* Return true if the next sequence of text is a number:
|
|
* either an integer, fixed point, or float with exponent. Stops scanning
|
|
* at the first non-number character, even if it is not whitespace.
|
|
*
|
|
* @param cp is the start of the current token.
|
|
* @param limit is the end of the current token.
|
|
* @return true if input token is a number, else false.
|
|
*/
|
|
static bool isNumber( const char* cp, const char* limit )
|
|
{
|
|
// regex for a float: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?" i.e. any number,
|
|
// code traversal manually here:
|
|
|
|
bool sawNumber = false;
|
|
|
|
if( cp < limit && ( *cp=='-' || *cp=='+' ) )
|
|
++cp;
|
|
|
|
while( cp < limit && isDigit( *cp ) )
|
|
{
|
|
++cp;
|
|
sawNumber = true;
|
|
}
|
|
|
|
if( cp < limit && *cp == '.' )
|
|
{
|
|
++cp;
|
|
|
|
while( cp < limit && isDigit( *cp ) )
|
|
{
|
|
++cp;
|
|
sawNumber = true;
|
|
}
|
|
}
|
|
|
|
if( sawNumber )
|
|
{
|
|
if( cp < limit && ( *cp=='E' || *cp=='e' ) )
|
|
{
|
|
++cp;
|
|
|
|
sawNumber = false; // exponent mandates at least one digit thereafter.
|
|
|
|
if( cp < limit && ( *cp=='-' || *cp=='+' ) )
|
|
++cp;
|
|
|
|
while( cp < limit && isDigit( *cp ) )
|
|
{
|
|
++cp;
|
|
sawNumber = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sawNumber && cp==limit;
|
|
}
|
|
|
|
|
|
int DSNLEXER::NextTok()
|
|
{
|
|
const char* cur = next;
|
|
const char* head = cur;
|
|
|
|
prevTok = curTok;
|
|
|
|
if( curTok == DSN_EOF )
|
|
goto exit;
|
|
|
|
if( cur >= limit )
|
|
{
|
|
L_read:
|
|
// blank lines are returned as "\n" and will have a len of 1.
|
|
// EOF will have a len of 0 and so is detectable.
|
|
int len = readLine();
|
|
|
|
if( len == 0 )
|
|
{
|
|
cur = start; // after readLine(), since start can change, set cur offset to start
|
|
curTok = DSN_EOF;
|
|
goto exit;
|
|
}
|
|
|
|
cur = start; // after readLine() since start can change.
|
|
|
|
// skip leading whitespace
|
|
while( cur < limit && isSpace( *cur ) )
|
|
++cur;
|
|
|
|
// If the first non-blank character is #, this line is a comment.
|
|
// Comments cannot follow any other token on the same line.
|
|
if( cur<limit && *cur=='#' )
|
|
{
|
|
if( commentsAreTokens )
|
|
{
|
|
// Grab the entire current line [excluding end of line char(s)] as the
|
|
// current token. The '#' character may not be at offset zero.
|
|
|
|
while( limit[-1] == '\n' || limit[-1] == '\r' )
|
|
--limit;
|
|
|
|
curText.clear();
|
|
curText.append( start, limit );
|
|
|
|
cur = start; // ensure a good curOffset below
|
|
curTok = DSN_COMMENT;
|
|
head = limit; // do a readLine() on next call in here.
|
|
goto exit;
|
|
}
|
|
else
|
|
{
|
|
goto L_read;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// skip leading whitespace
|
|
while( cur < limit && isSpace( *cur ) )
|
|
++cur;
|
|
}
|
|
|
|
if( cur >= limit )
|
|
goto L_read;
|
|
|
|
if( *cur == '(' )
|
|
{
|
|
curText = *cur;
|
|
curTok = DSN_LEFT;
|
|
head = cur+1;
|
|
goto exit;
|
|
}
|
|
|
|
if( *cur == ')' )
|
|
{
|
|
curText = *cur;
|
|
curTok = DSN_RIGHT;
|
|
head = cur+1;
|
|
goto exit;
|
|
}
|
|
|
|
// Non-specctraMode, understands and deciphers escaped \, \r, \n, and \".
|
|
// Strips off leading and trailing double quotes
|
|
if( !specctraMode )
|
|
{
|
|
// a quoted string, will return DSN_STRING
|
|
if( *cur == stringDelimiter )
|
|
{
|
|
// copy the token, character by character so we can remove doubled up quotes.
|
|
curText.clear();
|
|
|
|
++cur; // skip over the leading delimiter, which is always " in non-specctraMode
|
|
|
|
head = cur;
|
|
|
|
while( head<limit )
|
|
{
|
|
// ESCAPE SEQUENCES:
|
|
if( *head =='\\' )
|
|
{
|
|
char tbuf[8];
|
|
char c;
|
|
int i;
|
|
|
|
if( ++head >= limit )
|
|
break; // throw exception at L_unterminated
|
|
|
|
switch( *head++ )
|
|
{
|
|
case '"':
|
|
case '\\': c = head[-1]; break;
|
|
case 'a': c = '\x07'; break;
|
|
case 'b': c = '\x08'; break;
|
|
case 'f': c = '\x0c'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\x09'; break;
|
|
case 'v': c = '\x0b'; break;
|
|
|
|
case 'x': // 1 or 2 byte hex escape sequence
|
|
for( i = 0; i < 2; ++i )
|
|
{
|
|
if( !isxdigit( head[i] ) )
|
|
break;
|
|
|
|
tbuf[i] = head[i];
|
|
}
|
|
|
|
tbuf[i] = '\0';
|
|
|
|
if( i > 0 )
|
|
c = (char) strtoul( tbuf, nullptr, 16 );
|
|
else
|
|
c = 'x'; // a goofed hex escape sequence, interpret as 'x'
|
|
|
|
head += i;
|
|
break;
|
|
|
|
default: // 1-3 byte octal escape sequence
|
|
--head;
|
|
|
|
for( i=0; i<3; ++i )
|
|
{
|
|
if( head[i] < '0' || head[i] > '7' )
|
|
break;
|
|
|
|
tbuf[i] = head[i];
|
|
}
|
|
|
|
tbuf[i] = '\0';
|
|
|
|
if( i > 0 )
|
|
c = (char) strtoul( tbuf, nullptr, 8 );
|
|
else
|
|
c = '\\'; // a goofed octal escape sequence, interpret as '\'
|
|
|
|
head += i;
|
|
break;
|
|
}
|
|
|
|
curText += c;
|
|
}
|
|
|
|
else if( *head == '"' ) // end of the non-specctraMode DSN_STRING
|
|
{
|
|
curTok = DSN_STRING;
|
|
++head; // omit this trailing double quote
|
|
goto exit;
|
|
}
|
|
|
|
else
|
|
curText += *head++;
|
|
|
|
} // while
|
|
|
|
// L_unterminated:
|
|
wxString errtxt( _( "Un-terminated delimited string" ) );
|
|
THROW_PARSE_ERROR( errtxt, CurSource(), CurLine(), CurLineNumber(),
|
|
cur - start + curText.length() );
|
|
}
|
|
}
|
|
else // is specctraMode, tests in this block should not occur in KiCad mode.
|
|
{
|
|
/* get the dash out of a <pin_reference> which is embedded for example
|
|
like: U2-14 or "U2"-"14"
|
|
This is detectable by a non-space immediately preceding the dash.
|
|
*/
|
|
if( *cur == '-' && cur>start && !isSpace( cur[-1] ) )
|
|
{
|
|
curText = '-';
|
|
curTok = DSN_DASH;
|
|
head = cur+1;
|
|
goto exit;
|
|
}
|
|
|
|
// switching the string_quote character
|
|
if( prevTok == DSN_STRING_QUOTE )
|
|
{
|
|
static const wxString errtxt( _("String delimiter must be a single character of "
|
|
"', \", or $") );
|
|
|
|
char cc = *cur;
|
|
switch( cc )
|
|
{
|
|
case '\'':
|
|
case '$':
|
|
case '"':
|
|
break;
|
|
default:
|
|
THROW_PARSE_ERROR( errtxt, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
curText = cc;
|
|
|
|
head = cur+1;
|
|
|
|
if( head<limit && !isSep( *head ) )
|
|
{
|
|
THROW_PARSE_ERROR( errtxt, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
curTok = DSN_QUOTE_DEF;
|
|
goto exit;
|
|
}
|
|
|
|
// specctraMode DSN_STRING
|
|
if( *cur == stringDelimiter )
|
|
{
|
|
++cur; // skip over the leading delimiter: ",', or $
|
|
|
|
head = cur;
|
|
|
|
while( head<limit && !isStringTerminator( *head ) )
|
|
++head;
|
|
|
|
if( head >= limit )
|
|
{
|
|
wxString errtxt( _( "Un-terminated delimited string" ) );
|
|
THROW_PARSE_ERROR( errtxt, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
curText.clear();
|
|
curText.append( cur, head );
|
|
|
|
++head; // skip over the trailing delimiter
|
|
|
|
curTok = DSN_STRING;
|
|
goto exit;
|
|
}
|
|
} // specctraMode
|
|
|
|
// non-quoted token, read it into curText.
|
|
curText.clear();
|
|
|
|
head = cur;
|
|
while( head<limit && !isSep( *head ) )
|
|
curText += *head++;
|
|
|
|
if( isNumber( curText.c_str(), curText.c_str() + curText.size() ) )
|
|
{
|
|
curTok = DSN_NUMBER;
|
|
goto exit;
|
|
}
|
|
|
|
if( specctraMode && curText == "string_quote" )
|
|
{
|
|
curTok = DSN_STRING_QUOTE;
|
|
goto exit;
|
|
}
|
|
|
|
curTok = findToken( curText );
|
|
|
|
exit: // single point of exit, no returns elsewhere please.
|
|
|
|
curOffset = cur - start;
|
|
|
|
next = head;
|
|
|
|
return curTok;
|
|
}
|
|
|
|
|
|
wxArrayString* DSNLEXER::ReadCommentLines()
|
|
{
|
|
wxArrayString* ret = nullptr;
|
|
bool cmt_setting = SetCommentsAreTokens( true );
|
|
int tok = NextTok();
|
|
|
|
if( tok == DSN_COMMENT )
|
|
{
|
|
ret = new wxArrayString();
|
|
|
|
do
|
|
{
|
|
ret->Add( FromUTF8() );
|
|
}
|
|
while( ( tok = NextTok() ) == DSN_COMMENT );
|
|
}
|
|
|
|
SetCommentsAreTokens( cmt_setting );
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
double DSNLEXER::parseDouble()
|
|
{
|
|
#if ( defined( __GNUC__ ) && __GNUC__ < 11 ) || ( defined( __clang__ ) && __clang_major__ < 13 )
|
|
// GCC older than 11 "supports" C++17 without supporting the C++17 std::from_chars for doubles
|
|
// clang is similar
|
|
|
|
char* tmp;
|
|
|
|
errno = 0;
|
|
|
|
double fval = strtod( CurText(), &tmp );
|
|
|
|
if( errno )
|
|
{
|
|
wxString error;
|
|
error.Printf( _( "Invalid floating point number in\nfile: '%s'\nline: %d\noffset: %d" ),
|
|
CurSource(), CurLineNumber(), CurOffset() );
|
|
|
|
THROW_IO_ERROR( error );
|
|
}
|
|
|
|
if( CurText() == tmp )
|
|
{
|
|
wxString error;
|
|
error.Printf( _( "Missing floating point number in\nfile: '%s'\nline: %d\noffset: %d" ),
|
|
CurSource(), CurLineNumber(), CurOffset() );
|
|
|
|
THROW_IO_ERROR( error );
|
|
}
|
|
|
|
return fval;
|
|
#else
|
|
// Use std::from_chars which is designed to be locale independent and performance oriented for data interchange
|
|
|
|
const std::string& str = CurStr();
|
|
|
|
// Offset any leading whitespace, this is one thing from_chars does not handle
|
|
size_t woff = 0;
|
|
while( std::isspace( str[woff] ) && woff < str.length() )
|
|
{
|
|
woff++;
|
|
}
|
|
|
|
double dval{};
|
|
std::from_chars_result res =
|
|
std::from_chars( str.data() + woff, str.data() + str.size(), dval );
|
|
|
|
if( res.ec != std::errc() )
|
|
{
|
|
THROW_PARSE_ERROR( _( "Invalid floating point number" ), CurSource(), CurLine(),
|
|
CurLineNumber(), CurOffset() );
|
|
}
|
|
|
|
return dval;
|
|
#endif
|
|
}
|