diff --git a/common/libeval/numeric_evaluator.cpp b/common/libeval/numeric_evaluator.cpp index 34699670a4..9559a6bee4 100644 --- a/common/libeval/numeric_evaluator.cpp +++ b/common/libeval/numeric_evaluator.cpp @@ -192,11 +192,55 @@ void NUMERIC_EVALUATOR::newString( const wxString& aString ) } +// Support for old school decimal separators (ie: "2K2") +bool NUMERIC_EVALUATOR::IsOldSchoolDecimalSeparator( char ch, double* siScaler ) +{ + switch( ch ) + { + case 'a': *siScaler = 1.0e-18; return true; + case 'f': *siScaler = 1.0e-15; return true; + case 'p': *siScaler = 1.0e-12; return true; + case 'n': *siScaler = 1.0e-9; return true; + case 'u': *siScaler = 1.0e-6; return true; + case 'm': *siScaler = 1.0e-3; return true; + case 'k': + case 'K': *siScaler = 1.0e3; return true; + case 'M': *siScaler = 1.0e6; return true; + case 'G': *siScaler = 1.0e9; return true; + case 'T': *siScaler = 1.0e12; return true; + case 'P': *siScaler = 1.0e15; return true; + case 'E': *siScaler = 1.0e18; return true; + default: return false; + } +}; + + +bool NUMERIC_EVALUATOR::IsDecimalSeparator( char ch, char localeSeparator, bool allowInfixNotation ) +{ + double dummy; + + if( ch == localeSeparator || ch == '.' || ch == ',' ) + return true; + + if( allowInfixNotation && IsOldSchoolDecimalSeparator( ch, &dummy ) ) + return true; + + return false; +}; + + +bool NUMERIC_EVALUATOR::IsDigit( char ch ) +{ + // the below static cast is to avoid partial unicode chars triggering an assert in isdigit on msvc + return isdigit( static_cast( ch ) ); +} + + NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken() { - Token retval; - size_t idx; + bool allowInfix = m_defaultUnits == Unit::SI; + Token retval; retval.token = ENDS; retval.value.dValue = 0; retval.value.valid = false; @@ -211,43 +255,6 @@ NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken() if( m_token.pos >= m_token.inputLen ) return retval; - // Support for old school decimal separators (ie: "2K2") - auto isOldSchoolDecimalSeparator = - []( char ch, double* siScaler ) -> bool - { - switch( ch ) - { - case 'a': *siScaler = 1.0e-18; return true; - case 'f': *siScaler = 1.0e-15; return true; - case 'p': *siScaler = 1.0e-12; return true; - case 'n': *siScaler = 1.0e-9; return true; - case 'u': *siScaler = 1.0e-6; return true; - case 'm': *siScaler = 1.0e-3; return true; - case 'k': - case 'K': *siScaler = 1.0e3; return true; - case 'M': *siScaler = 1.0e6; return true; - case 'G': *siScaler = 1.0e9; return true; - case 'T': *siScaler = 1.0e12; return true; - case 'P': *siScaler = 1.0e15; return true; - case 'E': *siScaler = 1.0e18; return true; - default: return false; - } - }; - - auto isDecimalSeparator = - [&]( char ch ) -> bool - { - double dummy; - - if( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' ) - return true; - - if( m_defaultUnits == Unit::SI && isOldSchoolDecimalSeparator( ch, &dummy ) ) - return true; - - return false; - }; - // Lambda: get value as string, store into clToken.token and update current index. auto extractNumber = [&]( double* aScaler ) @@ -256,18 +263,18 @@ NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken() double siScaler = 1.0; char ch = m_token.input[ m_token.pos ]; - idx = 0; + size_t idx = 0; do { - if( isDecimalSeparator( ch ) ) + if( IsDecimalSeparator( ch, m_localeDecimalSeparator, allowInfix ) ) { if( haveSeparator ) break; else haveSeparator = true; - if( isOldSchoolDecimalSeparator( ch, &siScaler ) ) + if( IsOldSchoolDecimalSeparator( ch, &siScaler ) ) *aScaler = siScaler; m_token.token[ idx++ ] = m_localeDecimalSeparator; @@ -279,9 +286,7 @@ NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken() ch = m_token.input[++m_token.pos]; - // the below static cast is to avoid partial unicode chars triggering an - // assert in isdigit on msvc - } while( isdigit( static_cast( ch ) ) || isDecimalSeparator( ch ) ); + } while( IsDigit( ch ) || IsDecimalSeparator( ch, m_localeDecimalSeparator, allowInfix ) ); m_token.token[ idx ] = 0; }; @@ -385,8 +390,7 @@ NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken() return Unit::Mil; } - if( m_defaultUnits == Unit::SI && sizeLeft >= 1 - && isOldSchoolDecimalSeparator( ch, siScaler ) ) + if( allowInfix && sizeLeft >= 1 && IsOldSchoolDecimalSeparator( ch, siScaler ) ) { m_token.pos++; return Unit::SI; @@ -415,10 +419,8 @@ NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken() { /* End of input */ } - else if( isdigit( static_cast( ch ) ) || isDecimalSeparator( ch ) ) + else if( IsDigit( ch ) || IsDecimalSeparator( ch, m_localeDecimalSeparator, allowInfix ) ) { - // the above static cast is to avoid partial unicode chars triggering an assert in - // isdigit on msvc // VALUE extractNumber( &siScaler ); retval.token = VALUE; diff --git a/common/string_utils.cpp b/common/string_utils.cpp index 12e955a3e5..e522c657db 100644 --- a/common/string_utils.cpp +++ b/common/string_utils.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include "locale_io.h" @@ -974,7 +975,7 @@ bool WildCompareString( const wxString& pattern, const wxString& string_to_tst, bool ApplyModifier( double& value, const wxString& aString ) { /// Although the two 'μ's look the same, they are U+03BC and U+00B5 - static const wxString modifiers( wxT( "pnuµμmkKM" ) ); + static const wxString modifiers( wxT( "afpnuµμmkKMGTPE" ) ); if( !aString.length() ) return false; @@ -1004,6 +1005,10 @@ bool ApplyModifier( double& value, const wxString& aString ) return false; } + if( modifier == 'a' ) + value *= 1.0e-18; + else if( modifier == 'f' ) + value *= 1.0e-15; if( modifier == 'p' ) value *= 1.0e-12; if( modifier == 'n' ) @@ -1018,6 +1023,12 @@ bool ApplyModifier( double& value, const wxString& aString ) value *= 1.0e6; else if( modifier == 'G' ) value *= 1.0e9; + else if( modifier == 'T' ) + value *= 1.0e12; + else if( modifier == 'P' ) + value *= 1.0e15; + else if( modifier == 'E' ) + value *= 1.0e18; return true; } @@ -1203,6 +1214,7 @@ int SplitString( const wxString& strToSplit, wxString* strEnd ) { static const wxString separators( wxT( ".," ) ); + wxUniChar infix = 0; // Clear all the return strings strBeginning->Empty(); @@ -1237,8 +1249,26 @@ int SplitString( const wxString& strToSplit, for( ; ii >= 0; ii-- ) { - if( !wxIsdigit( strToSplit[ii] ) && separators.Find( strToSplit[ii] ) < 0 ) + double dummy; + wxUniChar c = strToSplit[ii]; + + if( wxIsdigit( c ) ) + { + continue; + } + if( infix == 0 && NUMERIC_EVALUATOR::IsOldSchoolDecimalSeparator( c, &dummy ) ) + { + infix = c; + continue; + } + else if( separators.Find( strToSplit[ii] ) >= 0 ) + { + continue; + } + else + { break; + } } // If all that was left was digits, then just set the digits string @@ -1252,6 +1282,12 @@ int SplitString( const wxString& strToSplit, *strDigits = strToSplit.substr( ii + 1, position - ii - 1 ); *strBeginning = strToSplit.substr( 0, ii + 1 ); } + + if( infix > 0 ) + { + strDigits->Replace( infix, '.' ); + *strEnd = infix + *strEnd; + } } return 0; diff --git a/include/libeval/numeric_evaluator.h b/include/libeval/numeric_evaluator.h index 29839751cf..535b0b17c8 100644 --- a/include/libeval/numeric_evaluator.h +++ b/include/libeval/numeric_evaluator.h @@ -156,6 +156,10 @@ public: /* Remove all variables */ void ClearVar() { m_varMap.clear(); } + static bool IsOldSchoolDecimalSeparator( char ch, double* siScaler ); + static bool IsDecimalSeparator( char ch, char localeSeparator, bool allowInfixNotation ); + static bool IsDigit( char ch ); + protected: /* Token type used by the tokenizer */ struct Token diff --git a/qa/tests/common/test_kicad_string.cpp b/qa/tests/common/test_kicad_string.cpp index 73942e2814..2d9313d463 100644 --- a/qa/tests/common/test_kicad_string.cpp +++ b/qa/tests/common/test_kicad_string.cpp @@ -116,6 +116,30 @@ BOOST_AUTO_TEST_CASE( NaturalNumberCompare ) } +BOOST_AUTO_TEST_CASE( ValueCompare ) +{ + using CASE = std::pair, int>; + + const std::vector cases = { + { { "100", "10" }, 1 }, + { { "10K", "1K" }, 1 }, + { { "10K", "1K5" }, 1 }, + { { "10K", "10,000" }, 0 }, + { { "1K5", "1.5K" }, 0 }, + { { "1K5", "1,5K" }, 0 }, + { { "K5", "1K" }, -1 }, + { { "1K5", "K55" }, 1 }, + { { "1u5F", "1.5uF" }, 0 }, + }; + + for( const auto& c : cases ) + { + BOOST_CHECK_MESSAGE( ValueStringCompare( c.first.first, c.first.second ) == c.second, + c.first.first + " AND " + c.first.second + " failed" ); + } +} + + /** * Test the #GetTrailingInt method. */