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.

372 lines
9.6 KiB

8 years ago
  1. /*
  2. This file is part of libeval, a simple math expression evaluator
  3. Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. */
  15. #include <libeval/numeric_evaluator.h>
  16. /* The (generated) lemon parser is written in C.
  17. * In order to keep its symbol from the global namespace include the parser code with
  18. * a C++ namespace.
  19. */
  20. namespace numEval
  21. {
  22. #ifdef __GNUC__
  23. #pragma GCC diagnostic push
  24. #pragma GCC diagnostic ignored "-Wunused-variable"
  25. #pragma GCC diagnostic ignored "-Wsign-compare"
  26. #endif
  27. #include "grammar.c"
  28. #include "grammar.h"
  29. #ifdef __GNUC__
  30. #pragma GCC diagnostic pop
  31. #endif
  32. } /* namespace numEval */
  33. NUMERIC_EVALUATOR::NUMERIC_EVALUATOR( EDA_UNITS_T aUnits, bool aUseMils )
  34. {
  35. struct lconv* lc = localeconv();
  36. m_localeDecimalSeparator = *lc->decimal_point;
  37. m_parseError = false;
  38. m_parseFinished = false;
  39. m_parser = numEval::ParseAlloc( malloc );
  40. switch( aUnits )
  41. {
  42. case INCHES:
  43. if( aUseMils )
  44. m_defaultUnits = Unit::Mil;
  45. else
  46. m_defaultUnits = Unit::Inch;
  47. break;
  48. case MILLIMETRES:m_defaultUnits = Unit::Metric;
  49. break;
  50. default:m_defaultUnits = Unit::Metric;
  51. break;
  52. }
  53. }
  54. NUMERIC_EVALUATOR::~NUMERIC_EVALUATOR()
  55. {
  56. numEval::ParseFree( m_parser, free );
  57. // Allow explicit call to destructor
  58. m_parser = nullptr;
  59. Clear();
  60. }
  61. void NUMERIC_EVALUATOR::Clear()
  62. {
  63. free( m_token.token );
  64. m_token.token = nullptr;
  65. m_token.input = nullptr;
  66. m_parseError = true;
  67. m_originalText = wxEmptyString;
  68. }
  69. void NUMERIC_EVALUATOR::parseError( const char* s )
  70. {
  71. m_parseError = true;
  72. }
  73. void NUMERIC_EVALUATOR::parseOk()
  74. {
  75. m_parseFinished = true;
  76. }
  77. void NUMERIC_EVALUATOR::parseSetResult( double val )
  78. {
  79. snprintf( m_token.token, m_token.OutLen, "%.10g", val );
  80. }
  81. wxString NUMERIC_EVALUATOR::OriginalText() const
  82. {
  83. return m_originalText;
  84. }
  85. bool NUMERIC_EVALUATOR::Process( const wxString& aString )
  86. {
  87. // Feed parser token after token until end of input.
  88. newString( aString );
  89. m_parseError = false;
  90. m_parseFinished = false;
  91. Token tok;
  92. if( aString.IsEmpty() )
  93. {
  94. m_parseFinished = true;
  95. return true;
  96. }
  97. do
  98. {
  99. tok = getToken();
  100. numEval::Parse( m_parser, tok.token, tok.value, this );
  101. if( m_parseFinished || tok.token == ENDS )
  102. {
  103. // Reset parser by passing zero as token ID, value is ignored.
  104. numEval::Parse( m_parser, 0, tok.value, this );
  105. break;
  106. }
  107. } while( tok.token );
  108. return !m_parseError;
  109. }
  110. void NUMERIC_EVALUATOR::newString( const wxString& aString )
  111. {
  112. Clear();
  113. m_originalText = aString;
  114. m_token.token = reinterpret_cast<decltype( m_token.token )>( malloc( TokenStat::OutLen + 1 ) );
  115. strcpy( m_token.token, "0" );
  116. m_token.inputLen = aString.length();
  117. m_token.pos = 0;
  118. m_token.input = aString.mb_str();
  119. m_parseFinished = false;
  120. }
  121. NUMERIC_EVALUATOR::Token NUMERIC_EVALUATOR::getToken()
  122. {
  123. Token retval;
  124. size_t idx;
  125. retval.token = ENDS;
  126. retval.value.dValue = 0;
  127. if( m_token.token == nullptr )
  128. return retval;
  129. if( m_token.input == nullptr )
  130. return retval;
  131. if( m_token.pos >= m_token.inputLen )
  132. return retval;
  133. auto isDecimalSeparator = [ & ]( char ch ) -> bool {
  134. return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
  135. };
  136. // Lambda: get value as string, store into clToken.token and update current index.
  137. auto extractNumber = [ & ]() {
  138. bool haveSeparator = false;
  139. idx = 0;
  140. auto ch = m_token.input[ m_token.pos ];
  141. do
  142. {
  143. if( isDecimalSeparator( ch ) && haveSeparator )
  144. break;
  145. m_token.token[ idx++ ] = ch;
  146. if( isDecimalSeparator( ch ))
  147. haveSeparator = true;
  148. ch = m_token.input[ ++m_token.pos ];
  149. } while( isdigit( ch ) || isDecimalSeparator( ch ));
  150. m_token.token[ idx ] = 0;
  151. // Ensure that the systems decimal separator is used
  152. for( int i = strlen( m_token.token ); i; i-- )
  153. if( isDecimalSeparator( m_token.token[ i - 1 ] ))
  154. m_token.token[ i - 1 ] = m_localeDecimalSeparator;
  155. };
  156. // Lamda: Get unit for current token.
  157. // Valid units are ", in, mm, mil and thou. Returns Unit::Invalid otherwise.
  158. auto checkUnit = [ this ]() -> Unit {
  159. char ch = m_token.input[ m_token.pos ];
  160. if( ch == '"' )
  161. {
  162. m_token.pos++;
  163. return Unit::Inch;
  164. }
  165. // Do not use strcasecmp() as it is not available on all platforms
  166. const char* cptr = &m_token.input[ m_token.pos ];
  167. const auto sizeLeft = m_token.inputLen - m_token.pos;
  168. if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ))
  169. {
  170. m_token.pos += 2;
  171. return Unit::Metric;
  172. }
  173. if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ))
  174. {
  175. m_token.pos += 2;
  176. return Unit::Inch;
  177. }
  178. if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l' && !isalnum( cptr[ 3 ] ))
  179. {
  180. m_token.pos += 3;
  181. return Unit::Mil;
  182. }
  183. if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o' && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ))
  184. {
  185. m_token.pos += 4;
  186. return Unit::Mil;
  187. }
  188. return Unit::Invalid;
  189. };
  190. char ch;
  191. // Start processing of first/next token: Remove whitespace
  192. for( ;; )
  193. {
  194. ch = m_token.input[ m_token.pos ];
  195. if( ch == ' ' )
  196. m_token.pos++;
  197. else
  198. break;
  199. }
  200. Unit convertFrom;
  201. if( ch == 0 )
  202. {
  203. /* End of input */
  204. }
  205. else if( isdigit( ch ) || isDecimalSeparator( ch ))
  206. {
  207. // VALUE
  208. extractNumber();
  209. retval.token = VALUE;
  210. retval.value.dValue = atof( m_token.token );
  211. }
  212. else if(( convertFrom = checkUnit()) != Unit::Invalid )
  213. {
  214. // UNIT
  215. // Units are appended to a VALUE.
  216. // Determine factor to default unit if unit for value is given.
  217. // Example: Default is mm, unit is inch: factor is 25.4
  218. // The factor is assigned to the terminal UNIT. The actual
  219. // conversion is done within a parser action.
  220. retval.token = UNIT;
  221. if( m_defaultUnits == Unit::Metric )
  222. {
  223. switch( convertFrom )
  224. {
  225. case Unit::Inch :retval.value.dValue = 25.4; break;
  226. case Unit::Mil :retval.value.dValue = 25.4 / 1000.0; break;
  227. case Unit::Metric :retval.value.dValue = 1.0; break;
  228. case Unit::Invalid :break;
  229. }
  230. }
  231. else if( m_defaultUnits == Unit::Inch )
  232. {
  233. switch( convertFrom )
  234. {
  235. case Unit::Inch :retval.value.dValue = 1.0; break;
  236. case Unit::Mil :retval.value.dValue = 1.0 / 1000.0; break;
  237. case Unit::Metric :retval.value.dValue = 1.0 / 25.4; break;
  238. case Unit::Invalid :break;
  239. }
  240. }
  241. else if( m_defaultUnits == Unit::Mil )
  242. {
  243. switch( convertFrom )
  244. {
  245. case Unit::Inch :retval.value.dValue = 1.0 * 1000.0; break;
  246. case Unit::Mil :retval.value.dValue = 1.0; break;
  247. case Unit::Metric :retval.value.dValue = 1000.0 / 25.4; break;
  248. case Unit::Invalid :break;
  249. }
  250. }
  251. }
  252. else if( isalpha( ch ))
  253. {
  254. // VAR
  255. const char* cptr = &m_token.input[ m_token.pos ];
  256. cptr++;
  257. while( isalnum( *cptr ))
  258. cptr++;
  259. retval.token = VAR;
  260. size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
  261. if( bytesToCopy >= sizeof( retval.value.text ))
  262. bytesToCopy = sizeof( retval.value.text ) - 1;
  263. strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
  264. retval.value.text[ bytesToCopy ] = 0;
  265. m_token.pos += cptr - &m_token.input[ m_token.pos ];
  266. }
  267. else
  268. {
  269. // Single char tokens
  270. switch( ch )
  271. {
  272. case '+' :retval.token = PLUS; break;
  273. case '-' :retval.token = MINUS; break;
  274. case '*' :retval.token = MULT; break;
  275. case '/' :retval.token = DIVIDE; break;
  276. case '(' :retval.token = PARENL; break;
  277. case ')' :retval.token = PARENR; break;
  278. case '=' :retval.token = ASSIGN; break;
  279. case ';' :retval.token = SEMCOL; break;
  280. default :m_parseError = true; break; /* invalid character */
  281. }
  282. m_token.pos++;
  283. }
  284. return retval;
  285. }
  286. void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
  287. {
  288. m_varMap[ aString ] = aValue;
  289. }
  290. double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
  291. {
  292. if( m_varMap[ aString ] )
  293. return m_varMap[ aString ];
  294. else
  295. return 0.0;
  296. }