mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
This moves the generated files out of the source tree and into the build directory. They are now regenerated each time they are needed, based on the timestamp of the generated file compared to the timestamp of the lemon file. To do this, we also bundle lemon into the thirdparty directory and build it for ourselves since it is a very tiny program and not all platforms seem to distribute it in a consistent manner. Fixes https://gitlab.com/kicad/code/kicad/issues/5013
393 lines
10 KiB
C++
393 lines
10 KiB
C++
/*
|
|
This file is part of libeval, a simple math expression evaluator
|
|
|
|
Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com
|
|
|
|
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 3 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, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include <libeval/numeric_evaluator.h>
|
|
|
|
/* The (generated) lemon parser is written in C.
|
|
* In order to keep its symbol from the global namespace include the parser code with
|
|
* a C++ namespace.
|
|
*/
|
|
namespace numEval
|
|
{
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-variable"
|
|
#pragma GCC diagnostic ignored "-Wsign-compare"
|
|
#endif
|
|
|
|
#include <libeval/grammar.c>
|
|
#include <libeval/grammar.h>
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
} /* namespace numEval */
|
|
|
|
|
|
NUMERIC_EVALUATOR::NUMERIC_EVALUATOR( EDA_UNITS aUnits, bool aUseMils )
|
|
{
|
|
struct lconv* lc = localeconv();
|
|
m_localeDecimalSeparator = *lc->decimal_point;
|
|
|
|
m_parseError = false;
|
|
m_parseFinished = false;
|
|
|
|
m_parser = numEval::ParseAlloc( malloc );
|
|
|
|
switch( aUnits )
|
|
{
|
|
case EDA_UNITS::INCHES:
|
|
if( aUseMils )
|
|
m_defaultUnits = Unit::Mil;
|
|
else
|
|
m_defaultUnits = Unit::Inch;
|
|
break;
|
|
case EDA_UNITS::MILLIMETRES:
|
|
m_defaultUnits = Unit::MM;
|
|
break;
|
|
default:m_defaultUnits = Unit::MM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
NUMERIC_EVALUATOR::~NUMERIC_EVALUATOR()
|
|
{
|
|
numEval::ParseFree( m_parser, free );
|
|
|
|
// Allow explicit call to destructor
|
|
m_parser = nullptr;
|
|
|
|
Clear();
|
|
}
|
|
|
|
|
|
void NUMERIC_EVALUATOR::Clear()
|
|
{
|
|
free( m_token.token );
|
|
m_token.token = nullptr;
|
|
m_token.input = nullptr;
|
|
m_parseError = true;
|
|
m_originalText = wxEmptyString;
|
|
}
|
|
|
|
|
|
void NUMERIC_EVALUATOR::parseError( const char* s )
|
|
{
|
|
m_parseError = true;
|
|
}
|
|
|
|
|
|
void NUMERIC_EVALUATOR::parseOk()
|
|
{
|
|
m_parseFinished = true;
|
|
}
|
|
|
|
|
|
void NUMERIC_EVALUATOR::parseSetResult( double val )
|
|
{
|
|
if( std::isnan( val ) )
|
|
{
|
|
// Naively printing this with %g produces "nan" on some platforms
|
|
// and "-nan(ind)" on others (e.g. MSVC). So force a "standard" string.
|
|
snprintf( m_token.token, m_token.OutLen, "%s", "NaN" );
|
|
}
|
|
else
|
|
{
|
|
// Can be printed as a floating point
|
|
snprintf( m_token.token, m_token.OutLen, "%.10g", val );
|
|
}
|
|
}
|
|
|
|
|
|
wxString NUMERIC_EVALUATOR::OriginalText() const
|
|
{
|
|
return m_originalText;
|
|
}
|
|
|
|
|
|
bool NUMERIC_EVALUATOR::Process( const wxString& aString )
|
|
{
|
|
// Feed parser token after token until end of input.
|
|
|
|
newString( aString );
|
|
m_parseError = false;
|
|
m_parseFinished = false;
|
|
Token tok;
|
|
|
|
if( aString.IsEmpty() )
|
|
{
|
|
m_parseFinished = true;
|
|
return true;
|
|
}
|
|
|
|
do
|
|
{
|
|
tok = getToken();
|
|
numEval::Parse( m_parser, tok.token, tok.value, this );
|
|
|
|
if( m_parseFinished || tok.token == ENDS )
|
|
{
|
|
// Reset parser by passing zero as token ID, value is ignored.
|
|
numEval::Parse( m_parser, 0, tok.value, this );
|
|
break;
|
|
}
|
|
} while( tok.token );
|
|
|
|
return !m_parseError;
|
|
}
|
|
|
|
|
|
void NUMERIC_EVALUATOR::newString( const wxString& aString )
|
|
{
|
|
Clear();
|
|
|
|
m_originalText = aString;
|
|
|
|
m_token.token = reinterpret_cast<decltype( m_token.token )>( malloc( TokenStat::OutLen + 1 ) );
|
|
strcpy( m_token.token, "0" );
|
|
m_token.inputLen = aString.length();
|
|
m_token.pos = 0;
|
|
m_token.input = aString.mb_str();
|
|
|
|
m_parseFinished = false;
|
|
}
|
|
|
|
|
|
NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken()
|
|
{
|
|
Token retval;
|
|
size_t idx;
|
|
|
|
retval.token = ENDS;
|
|
retval.value.dValue = 0;
|
|
|
|
if( m_token.token == nullptr )
|
|
return retval;
|
|
|
|
if( m_token.input == nullptr )
|
|
return retval;
|
|
|
|
if( m_token.pos >= m_token.inputLen )
|
|
return retval;
|
|
|
|
auto isDecimalSeparator = [ & ]( char ch ) -> bool {
|
|
return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
|
|
};
|
|
|
|
// Lambda: get value as string, store into clToken.token and update current index.
|
|
auto extractNumber = [ & ]() {
|
|
bool haveSeparator = false;
|
|
idx = 0;
|
|
auto ch = m_token.input[ m_token.pos ];
|
|
|
|
do
|
|
{
|
|
if( isDecimalSeparator( ch ) && haveSeparator )
|
|
break;
|
|
|
|
m_token.token[ idx++ ] = ch;
|
|
|
|
if( isDecimalSeparator( ch ))
|
|
haveSeparator = true;
|
|
|
|
ch = m_token.input[ ++m_token.pos ];
|
|
} while( isdigit( ch ) || isDecimalSeparator( ch ));
|
|
|
|
m_token.token[ idx ] = 0;
|
|
|
|
// Ensure that the systems decimal separator is used
|
|
for( int i = strlen( m_token.token ); i; i-- )
|
|
if( isDecimalSeparator( m_token.token[ i - 1 ] ))
|
|
m_token.token[ i - 1 ] = m_localeDecimalSeparator;
|
|
};
|
|
|
|
// Lamda: Get unit for current token.
|
|
// Valid units are ", in, mm, mil and thou. Returns Unit::Invalid otherwise.
|
|
auto checkUnit = [ this ]() -> Unit {
|
|
char ch = m_token.input[ m_token.pos ];
|
|
|
|
if( ch == '"' )
|
|
{
|
|
m_token.pos++;
|
|
return Unit::Inch;
|
|
}
|
|
|
|
// Do not use strcasecmp() as it is not available on all platforms
|
|
const char* cptr = &m_token.input[ m_token.pos ];
|
|
const auto sizeLeft = m_token.inputLen - m_token.pos;
|
|
|
|
if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ))
|
|
{
|
|
m_token.pos += 2;
|
|
return Unit::MM;
|
|
}
|
|
|
|
if( sizeLeft >= 2 && ch == 'c' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ))
|
|
{
|
|
m_token.pos += 2;
|
|
return Unit::CM;
|
|
}
|
|
|
|
if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ))
|
|
{
|
|
m_token.pos += 2;
|
|
return Unit::Inch;
|
|
}
|
|
|
|
if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l' && !isalnum( cptr[ 3 ] ))
|
|
{
|
|
m_token.pos += 3;
|
|
return Unit::Mil;
|
|
}
|
|
|
|
if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o' && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ))
|
|
{
|
|
m_token.pos += 4;
|
|
return Unit::Mil;
|
|
}
|
|
|
|
return Unit::Invalid;
|
|
};
|
|
|
|
char ch;
|
|
|
|
// Start processing of first/next token: Remove whitespace
|
|
for( ;; )
|
|
{
|
|
ch = m_token.input[ m_token.pos ];
|
|
|
|
if( ch == ' ' )
|
|
m_token.pos++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
Unit convertFrom;
|
|
|
|
if( ch == 0 )
|
|
{
|
|
/* End of input */
|
|
}
|
|
else if( isdigit( ch ) || isDecimalSeparator( ch ))
|
|
{
|
|
// VALUE
|
|
extractNumber();
|
|
retval.token = VALUE;
|
|
retval.value.dValue = atof( m_token.token );
|
|
}
|
|
else if(( convertFrom = checkUnit()) != Unit::Invalid )
|
|
{
|
|
// UNIT
|
|
// Units are appended to a VALUE.
|
|
// Determine factor to default unit if unit for value is given.
|
|
// Example: Default is mm, unit is inch: factor is 25.4
|
|
// The factor is assigned to the terminal UNIT. The actual
|
|
// conversion is done within a parser action.
|
|
retval.token = UNIT;
|
|
if( m_defaultUnits == Unit::MM )
|
|
{
|
|
switch( convertFrom )
|
|
{
|
|
case Unit::Inch :retval.value.dValue = 25.4; break;
|
|
case Unit::Mil :retval.value.dValue = 25.4 / 1000.0; break;
|
|
case Unit::MM :retval.value.dValue = 1.0; break;
|
|
case Unit::CM :retval.value.dValue = 10.0; break;
|
|
case Unit::Invalid :break;
|
|
}
|
|
}
|
|
else if( m_defaultUnits == Unit::Inch )
|
|
{
|
|
switch( convertFrom )
|
|
{
|
|
case Unit::Inch :retval.value.dValue = 1.0; break;
|
|
case Unit::Mil :retval.value.dValue = 1.0 / 1000.0; break;
|
|
case Unit::MM :retval.value.dValue = 1.0 / 25.4; break;
|
|
case Unit::CM :retval.value.dValue = 1.0 / 2.54; break;
|
|
case Unit::Invalid :break;
|
|
}
|
|
}
|
|
else if( m_defaultUnits == Unit::Mil )
|
|
{
|
|
switch( convertFrom )
|
|
{
|
|
case Unit::Inch :retval.value.dValue = 1.0 * 1000.0; break;
|
|
case Unit::Mil :retval.value.dValue = 1.0; break;
|
|
case Unit::MM :retval.value.dValue = 1000.0 / 25.4; break;
|
|
case Unit::CM :retval.value.dValue = 1000.0 / 2.54; break;
|
|
case Unit::Invalid :break;
|
|
}
|
|
}
|
|
}
|
|
else if( isalpha( ch ))
|
|
{
|
|
// VAR
|
|
const char* cptr = &m_token.input[ m_token.pos ];
|
|
cptr++;
|
|
|
|
while( isalnum( *cptr ))
|
|
cptr++;
|
|
|
|
retval.token = VAR;
|
|
size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
|
|
|
|
if( bytesToCopy >= sizeof( retval.value.text ))
|
|
bytesToCopy = sizeof( retval.value.text ) - 1;
|
|
|
|
strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
|
|
retval.value.text[ bytesToCopy ] = 0;
|
|
m_token.pos += cptr - &m_token.input[ m_token.pos ];
|
|
}
|
|
else
|
|
{
|
|
// Single char tokens
|
|
switch( ch )
|
|
{
|
|
case '+' :retval.token = PLUS; break;
|
|
case '-' :retval.token = MINUS; break;
|
|
case '*' :retval.token = MULT; break;
|
|
case '/' :retval.token = DIVIDE; break;
|
|
case '(' :retval.token = PARENL; break;
|
|
case ')' :retval.token = PARENR; break;
|
|
case '=' :retval.token = ASSIGN; break;
|
|
case ';' :retval.token = SEMCOL; break;
|
|
default :m_parseError = true; break; /* invalid character */
|
|
}
|
|
m_token.pos++;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
|
|
{
|
|
m_varMap[ aString ] = aValue;
|
|
}
|
|
|
|
double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
|
|
{
|
|
if( m_varMap[ aString ] )
|
|
return m_varMap[ aString ];
|
|
else
|
|
return 0.0;
|
|
}
|