You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

503 lines
15 KiB

8 years ago
3 years ago
3 years ago
3 years ago
  1. /*
  2. * This file is part of libeval, a simple math expression evaluator
  3. *
  4. * Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com
  5. * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. */
  20. #include <string_utils.h>
  21. #include <libeval/numeric_evaluator.h>
  22. /* The (generated) lemon parser is written in C.
  23. * In order to keep its symbol from the global namespace include the parser code with
  24. * a C++ namespace.
  25. */
  26. namespace numEval
  27. {
  28. #ifdef __GNUC__
  29. #pragma GCC diagnostic push
  30. #pragma GCC diagnostic ignored "-Wunused-variable"
  31. #pragma GCC diagnostic ignored "-Wsign-compare"
  32. #endif
  33. #include <libeval/grammar.c>
  34. #include <libeval/grammar.h>
  35. #ifdef __GNUC__
  36. #pragma GCC diagnostic pop
  37. #endif
  38. } /* namespace numEval */
  39. NUMERIC_EVALUATOR::NUMERIC_EVALUATOR( EDA_UNITS aUnits )
  40. {
  41. LocaleChanged();
  42. m_parseError = false;
  43. m_parseFinished = false;
  44. m_parser = numEval::ParseAlloc( malloc );
  45. SetDefaultUnits( aUnits );
  46. }
  47. NUMERIC_EVALUATOR::~NUMERIC_EVALUATOR()
  48. {
  49. numEval::ParseFree( m_parser, free );
  50. // Allow explicit call to destructor
  51. m_parser = nullptr;
  52. Clear();
  53. }
  54. void NUMERIC_EVALUATOR::Clear()
  55. {
  56. delete[] m_token.token;
  57. m_token.token = nullptr;
  58. m_token.input = nullptr;
  59. m_parseError = true;
  60. m_originalText = wxEmptyString;
  61. }
  62. void NUMERIC_EVALUATOR::SetDefaultUnits( EDA_UNITS aUnits )
  63. {
  64. switch( aUnits )
  65. {
  66. case EDA_UNITS::MILLIMETRES: m_defaultUnits = Unit::MM; break;
  67. case EDA_UNITS::MILS: m_defaultUnits = Unit::Mil; break;
  68. case EDA_UNITS::INCHES: m_defaultUnits = Unit::Inch; break;
  69. case EDA_UNITS::DEGREES: m_defaultUnits = Unit::Degrees; break;
  70. case EDA_UNITS::UNSCALED: m_defaultUnits = Unit::SI; break;
  71. default: m_defaultUnits = Unit::MM; break;
  72. }
  73. }
  74. void NUMERIC_EVALUATOR::LocaleChanged()
  75. {
  76. struct lconv* lc = localeconv();
  77. m_localeDecimalSeparator = *lc->decimal_point;
  78. }
  79. // NOT UNUSED. Called by LEMON grammar.
  80. void NUMERIC_EVALUATOR::parseError( const char* s )
  81. {
  82. m_parseError = true;
  83. }
  84. // NOT UNUSED. Called by LEMON grammar.
  85. void NUMERIC_EVALUATOR::parseOk()
  86. {
  87. m_parseFinished = true;
  88. }
  89. // NOT UNUSED. Called by LEMON grammar.
  90. void NUMERIC_EVALUATOR::parseSetResult( double val )
  91. {
  92. if( std::isnan( val ) )
  93. {
  94. // Naively printing this with %g produces "nan" on some platforms
  95. // and "-nan(ind)" on others (e.g. MSVC). So force a "standard" string.
  96. snprintf( m_token.token, m_token.outputLen, "%s", "NaN" );
  97. }
  98. else
  99. {
  100. // Can be printed as a floating point
  101. // Warning: DO NOT use a format like %f or %g, because they can create issues.
  102. // especially %g can generate an exponent, incompatible with UNIT_BINDER
  103. // Use the optimized UIDouble2Str
  104. snprintf( m_token.token, m_token.outputLen, "%s", UIDouble2Str( val ).c_str() );
  105. }
  106. }
  107. wxString NUMERIC_EVALUATOR::OriginalText() const
  108. {
  109. return m_originalText;
  110. }
  111. bool NUMERIC_EVALUATOR::Process( const wxString& aString )
  112. {
  113. // Feed parser token after token until end of input.
  114. newString( aString );
  115. m_parseError = false;
  116. m_parseFinished = false;
  117. Token tok;
  118. if( aString.IsEmpty() )
  119. {
  120. m_parseFinished = true;
  121. return true;
  122. }
  123. do
  124. {
  125. tok = getToken();
  126. numEval::Parse( m_parser, tok.token, tok.value, this );
  127. if( m_parseFinished || tok.token == ENDS )
  128. {
  129. // Reset parser by passing zero as token ID, value is ignored.
  130. numEval::Parse( m_parser, 0, tok.value, this );
  131. break;
  132. }
  133. } while( tok.token );
  134. return !m_parseError;
  135. }
  136. void NUMERIC_EVALUATOR::newString( const wxString& aString )
  137. {
  138. Clear();
  139. m_originalText = aString;
  140. m_token.input = aString.mb_str();
  141. m_token.inputLen = strlen( m_token.input );
  142. m_token.outputLen = std::max<std::size_t>( 64, m_token.inputLen + 1 );
  143. m_token.pos = 0;
  144. m_token.token = new char[m_token.outputLen]();
  145. m_token.token[0] = '0';
  146. m_parseFinished = false;
  147. }
  148. NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken()
  149. {
  150. Token retval;
  151. size_t idx;
  152. retval.token = ENDS;
  153. retval.value.dValue = 0;
  154. retval.value.valid = false;
  155. retval.value.text[0] = 0;
  156. if( m_token.token == nullptr )
  157. return retval;
  158. if( m_token.input == nullptr )
  159. return retval;
  160. if( m_token.pos >= m_token.inputLen )
  161. return retval;
  162. // Support for old school decimal separators (ie: "2K2")
  163. auto isOldSchoolDecimalSeparator =
  164. []( char ch, double* siScaler ) -> bool
  165. {
  166. switch( ch )
  167. {
  168. case 'a': *siScaler = 1.0e-18; return true;
  169. case 'f': *siScaler = 1.0e-15; return true;
  170. case 'p': *siScaler = 1.0e-12; return true;
  171. case 'n': *siScaler = 1.0e-9; return true;
  172. case 'u': *siScaler = 1.0e-6; return true;
  173. case 'm': *siScaler = 1.0e-3; return true;
  174. case 'k':
  175. case 'K': *siScaler = 1.0e3; return true;
  176. case 'M': *siScaler = 1.0e6; return true;
  177. case 'G': *siScaler = 1.0e9; return true;
  178. case 'T': *siScaler = 1.0e12; return true;
  179. case 'P': *siScaler = 1.0e15; return true;
  180. case 'E': *siScaler = 1.0e18; return true;
  181. default: return false;
  182. }
  183. };
  184. auto isDecimalSeparator =
  185. [&]( char ch ) -> bool
  186. {
  187. double dummy;
  188. if( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' )
  189. return true;
  190. if( m_defaultUnits == Unit::SI && isOldSchoolDecimalSeparator( ch, &dummy ) )
  191. return true;
  192. return false;
  193. };
  194. // Lambda: get value as string, store into clToken.token and update current index.
  195. auto extractNumber =
  196. [&]( double* aScaler )
  197. {
  198. bool haveSeparator = false;
  199. double siScaler = 1.0;
  200. char ch = m_token.input[ m_token.pos ];
  201. idx = 0;
  202. do
  203. {
  204. if( isDecimalSeparator( ch ) )
  205. {
  206. if( haveSeparator )
  207. break;
  208. else
  209. haveSeparator = true;
  210. if( isOldSchoolDecimalSeparator( ch, &siScaler ) )
  211. *aScaler = siScaler;
  212. m_token.token[ idx++ ] = m_localeDecimalSeparator;
  213. }
  214. else
  215. {
  216. m_token.token[ idx++ ] = ch;
  217. }
  218. ch = m_token.input[ ++m_token.pos ];
  219. } while( isdigit( ch ) || isDecimalSeparator( ch ) );
  220. m_token.token[ idx ] = 0;
  221. };
  222. // Lamda: Get unit for current token.
  223. // Valid units are ", in, um, cm, mm, mil and thou. Returns Unit::Invalid otherwise.
  224. auto checkUnit =
  225. [&]( double* siScaler ) -> Unit
  226. {
  227. char ch = m_token.input[ m_token.pos ];
  228. if( ch == '"' )
  229. {
  230. m_token.pos++;
  231. return Unit::Inch;
  232. }
  233. // Do not use strcasecmp() as it is not available on all platforms
  234. const char* cptr = &m_token.input[ m_token.pos ];
  235. const auto sizeLeft = m_token.inputLen - m_token.pos;
  236. // We should really give this unicode support
  237. if( sizeLeft >= 2 && ch == '\xC2' && cptr[1] == '\xB0' )
  238. {
  239. m_token.pos += 2;
  240. return Unit::Degrees;
  241. }
  242. // Ideally we should also handle the unicode characters that can be used for micro,
  243. // but unicode handling in this tokenizer doesn't work.
  244. // (e.g. add support for μm (µ is MICRO SIGN), µm (µ is GREEK SMALL LETTER MU) later)
  245. if( sizeLeft >= 2 && ch == 'u' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
  246. {
  247. m_token.pos += 2;
  248. return Unit::UM;
  249. }
  250. if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
  251. {
  252. m_token.pos += 2;
  253. return Unit::MM;
  254. }
  255. if( sizeLeft >= 2 && ch == 'c' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
  256. {
  257. m_token.pos += 2;
  258. return Unit::CM;
  259. }
  260. if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ) )
  261. {
  262. m_token.pos += 2;
  263. return Unit::Inch;
  264. }
  265. if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l'
  266. && !isalnum( cptr[ 3 ] ) )
  267. {
  268. m_token.pos += 3;
  269. return Unit::Mil;
  270. }
  271. if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o'
  272. && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ) )
  273. {
  274. m_token.pos += 4;
  275. return Unit::Mil;
  276. }
  277. if( m_defaultUnits == Unit::SI && sizeLeft >= 1
  278. && isOldSchoolDecimalSeparator( ch, siScaler ) )
  279. {
  280. m_token.pos++;
  281. return Unit::SI;
  282. }
  283. return Unit::Invalid;
  284. };
  285. char ch;
  286. // Start processing of first/next token: Remove whitespace
  287. for( ;; )
  288. {
  289. ch = m_token.input[ m_token.pos ];
  290. if( ch == ' ' )
  291. m_token.pos++;
  292. else
  293. break;
  294. }
  295. double siScaler = 1.0;
  296. Unit convertFrom = Unit::Invalid;
  297. if( ch == 0 )
  298. {
  299. /* End of input */
  300. }
  301. else if( isdigit( ch ) || isDecimalSeparator( ch ) )
  302. {
  303. // VALUE
  304. extractNumber( &siScaler );
  305. retval.token = VALUE;
  306. retval.value.dValue = atof( m_token.token ) * siScaler;
  307. }
  308. else if( ( convertFrom = checkUnit( &siScaler ) ) != Unit::Invalid )
  309. {
  310. // UNIT
  311. // Units are appended to a VALUE.
  312. // Determine factor to default unit if unit for value is given.
  313. // Example: Default is mm, unit is inch: factor is 25.4
  314. // The factor is assigned to the terminal UNIT. The actual
  315. // conversion is done within a parser action.
  316. retval.token = UNIT;
  317. if( m_defaultUnits == Unit::MM )
  318. {
  319. switch( convertFrom )
  320. {
  321. case Unit::Inch: retval.value.dValue = 25.4; break;
  322. case Unit::Mil: retval.value.dValue = 25.4 / 1000.0; break;
  323. case Unit::UM: retval.value.dValue = 1 / 1000.0; break;
  324. case Unit::MM: retval.value.dValue = 1.0; break;
  325. case Unit::CM: retval.value.dValue = 10.0; break;
  326. default:
  327. case Unit::Invalid: break;
  328. }
  329. }
  330. else if( m_defaultUnits == Unit::Inch )
  331. {
  332. switch( convertFrom )
  333. {
  334. case Unit::Inch: retval.value.dValue = 1.0; break;
  335. case Unit::Mil: retval.value.dValue = 1.0 / 1000.0; break;
  336. case Unit::UM: retval.value.dValue = 1.0 / 25400.0; break;
  337. case Unit::MM: retval.value.dValue = 1.0 / 25.4; break;
  338. case Unit::CM: retval.value.dValue = 1.0 / 2.54; break;
  339. default:
  340. case Unit::Invalid: break;
  341. }
  342. }
  343. else if( m_defaultUnits == Unit::Mil )
  344. {
  345. switch( convertFrom )
  346. {
  347. case Unit::Inch: retval.value.dValue = 1.0 * 1000.0; break;
  348. case Unit::Mil: retval.value.dValue = 1.0; break;
  349. case Unit::UM: retval.value.dValue = 1.0 / 25.4; break;
  350. case Unit::MM: retval.value.dValue = 1000.0 / 25.4; break;
  351. case Unit::CM: retval.value.dValue = 1000.0 / 2.54; break;
  352. default:
  353. case Unit::Invalid: break;
  354. }
  355. }
  356. else if( m_defaultUnits == Unit::Degrees && convertFrom == Unit::Degrees )
  357. {
  358. retval.value.dValue = 1.0;
  359. }
  360. else if( m_defaultUnits == Unit::SI )
  361. {
  362. retval.value.dValue = siScaler;
  363. }
  364. }
  365. else if( isalpha( ch ) )
  366. {
  367. // VAR
  368. const char* cptr = &m_token.input[ m_token.pos ];
  369. cptr++;
  370. while( isalnum( *cptr ) )
  371. cptr++;
  372. retval.token = VAR;
  373. size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
  374. if( bytesToCopy >= sizeof( retval.value.text ) )
  375. bytesToCopy = sizeof( retval.value.text ) - 1;
  376. strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
  377. retval.value.text[ bytesToCopy ] = 0;
  378. m_token.pos += cptr - &m_token.input[ m_token.pos ];
  379. }
  380. else
  381. {
  382. // Single char tokens
  383. switch( ch )
  384. {
  385. case '+': retval.token = PLUS; break;
  386. case '-': retval.token = MINUS; break;
  387. case '*': retval.token = MULT; break;
  388. case '/': retval.token = DIVIDE; break;
  389. case '(': retval.token = PARENL; break;
  390. case ')': retval.token = PARENR; break;
  391. case '=': retval.token = ASSIGN; break;
  392. case ';': retval.token = SEMCOL; break;
  393. default: m_parseError = true; break; /* invalid character */
  394. }
  395. m_token.pos++;
  396. }
  397. if( !m_parseError )
  398. retval.value.valid = true;
  399. return retval;
  400. }
  401. void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
  402. {
  403. m_varMap[ aString ] = aValue;
  404. }
  405. double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
  406. {
  407. auto it = m_varMap.find( aString );
  408. if( it != m_varMap.end() )
  409. {
  410. return it->second;
  411. }
  412. else
  413. {
  414. m_parseError = true;
  415. return 0.0;
  416. }
  417. }