KiCad PCB EDA Suite
numeric_evaluator.cpp
Go to the documentation of this file.
1 /*
2  This file is part of libeval, a simple math expression evaluator
3 
4  Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com
5 
6  This program is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19 
20 
22 
23 /* The (generated) lemon parser is written in C.
24  * In order to keep its symbol from the global namespace include the parser code with
25  * a C++ namespace.
26  */
27 namespace numEval
28 {
29 
30 #ifdef __GNUC__
31 #pragma GCC diagnostic push
32 #pragma GCC diagnostic ignored "-Wunused-variable"
33 #pragma GCC diagnostic ignored "-Wsign-compare"
34 #endif
35 
36 #include "grammar.c"
37 #include "grammar.h"
38 
39 #ifdef __GNUC__
40 #pragma GCC diagnostic pop
41 #endif
42 
43 } /* namespace numEval */
44 
45 
47 {
48  struct lconv* lc = localeconv();
49  m_localeDecimalSeparator = *lc->decimal_point;
50 
51  m_parseError = false;
52  m_parseFinished = false;
53 
55 
56  switch( aUnits )
57  {
58  case INCHES:
59  if( aUseMils )
61  else
63  break;
65  break;
67  break;
68  }
69 }
70 
71 
73 {
75 
76  // Allow explicit call to destructor
77  m_parser = nullptr;
78 
79  Clear();
80 }
81 
82 
84 {
85  free( m_token.token );
86  m_token.token = nullptr;
87  m_token.input = nullptr;
88  m_parseError = true;
89  m_originalText = wxEmptyString;
90 }
91 
92 
93 void NUMERIC_EVALUATOR::parseError( const char* s )
94 {
95  m_parseError = true;
96 }
97 
98 
100 {
101  m_parseFinished = true;
102 }
103 
106 {
107  if( std::isnan( val ) )
108  {
109  // Naively printing this with %g produces "nan" on some platforms
110  // and "-nan(ind)" on others (e.g. MSVC). So force a "standard" string.
111  snprintf( m_token.token, m_token.OutLen, "%s", "NaN" );
112  }
113  else
114  {
115  // Can be printed as a floating point
116  snprintf( m_token.token, m_token.OutLen, "%.10g", val );
117  }
118 }
122 {
124 }
125 
127 bool NUMERIC_EVALUATOR::Process( const wxString& aString )
128 {
129  // Feed parser token after token until end of input.
131  newString( aString );
132  m_parseError = false;
134  Token tok;
136  if( aString.IsEmpty() )
137  {
139  return true;
140  }
141 
142  do
143  {
144  tok = getToken();
145  numEval::Parse( m_parser, tok.token, tok.value, this );
147  if( m_parseFinished || tok.token == ENDS )
148  {
149  // Reset parser by passing zero as token ID, value is ignored.
150  numEval::Parse( m_parser, 0, tok.value, this );
151  break;
152  }
153  } while( tok.token );
154 
155  return !m_parseError;
156 }
159 void NUMERIC_EVALUATOR::newString( const wxString& aString )
160 {
161  Clear();
162 
163  m_originalText = aString;
165  m_token.token = reinterpret_cast<decltype( m_token.token )>( malloc( TokenStat::OutLen + 1 ) );
166  strcpy( m_token.token, "0" );
167  m_token.inputLen = aString.length();
169  m_token.input = aString.mb_str();
172 }
176 {
177  Token retval;
178  size_t idx;
179 
180  retval.token = ENDS;
181  retval.value.dValue = 0;
183  if( m_token.token == nullptr )
184  return retval;
186  if( m_token.input == nullptr )
187  return retval;
190  return retval;
191 
192  auto isDecimalSeparator = [ & ]( char ch ) -> bool {
193  return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
194  };
195 
196  // Lambda: get value as string, store into clToken.token and update current index.
197  auto extractNumber = [ & ]() {
198  bool haveSeparator = false;
199  idx = 0;
200  auto ch = m_token.input[ m_token.pos ];
201 
202  do
203  {
204  if( isDecimalSeparator( ch ) && haveSeparator )
205  break;
206 
207  m_token.token[ idx++ ] = ch;
209  if( isDecimalSeparator( ch ))
210  haveSeparator = true;
213  } while( isdigit( ch ) || isDecimalSeparator( ch ));
214 
215  m_token.token[ idx ] = 0;
216 
217  // Ensure that the systems decimal separator is used
218  for( int i = strlen( m_token.token ); i; i-- )
219  if( isDecimalSeparator( m_token.token[ i - 1 ] ))
221  };
223  // Lamda: Get unit for current token.
224  // Valid units are ", in, mm, mil and thou. Returns Unit::Invalid otherwise.
225  auto checkUnit = [ this ]() -> Unit {
226  char ch = m_token.input[ m_token.pos ];
228  if( ch == '"' )
229  {
230  m_token.pos++;
231  return Unit::Inch;
232  }
234  // Do not use strcasecmp() as it is not available on all platforms
235  const char* cptr = &m_token.input[ m_token.pos ];
236  const auto sizeLeft = m_token.inputLen - m_token.pos;
237 
238  if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ))
239  {
240  m_token.pos += 2;
241  return Unit::Metric;
242  }
243 
244  if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ))
245  {
246  m_token.pos += 2;
247  return Unit::Inch;
248  }
249 
250  if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l' && !isalnum( cptr[ 3 ] ))
251  {
252  m_token.pos += 3;
253  return Unit::Mil;
254  }
256  if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o' && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ))
257  {
258  m_token.pos += 4;
259  return Unit::Mil;
260  }
261 
263  };
264 
265  char ch;
266 
267  // Start processing of first/next token: Remove whitespace
268  for( ;; )
269  {
270  ch = m_token.input[ m_token.pos ];
272  if( ch == ' ' )
274  else
275  break;
276  }
278  Unit convertFrom;
280  if( ch == 0 )
281  {
282  /* End of input */
283  }
284  else if( isdigit( ch ) || isDecimalSeparator( ch ))
285  {
286  // VALUE
287  extractNumber();
288  retval.token = VALUE;
289  retval.value.dValue = atof( m_token.token );
290  }
291  else if(( convertFrom = checkUnit()) != Unit::Invalid )
292  {
293  // UNIT
294  // Units are appended to a VALUE.
295  // Determine factor to default unit if unit for value is given.
296  // Example: Default is mm, unit is inch: factor is 25.4
297  // The factor is assigned to the terminal UNIT. The actual
298  // conversion is done within a parser action.
299  retval.token = UNIT;
301  {
302  switch( convertFrom )
303  {
304  case Unit::Inch :retval.value.dValue = 25.4; break;
305  case Unit::Mil :retval.value.dValue = 25.4 / 1000.0; break;
306  case Unit::Metric :retval.value.dValue = 1.0; break;
307  case Unit::Invalid :break;
308  }
309  }
311  {
312  switch( convertFrom )
313  {
314  case Unit::Inch :retval.value.dValue = 1.0; break;
315  case Unit::Mil :retval.value.dValue = 1.0 / 1000.0; break;
316  case Unit::Metric :retval.value.dValue = 1.0 / 25.4; break;
317  case Unit::Invalid :break;
318  }
319  }
320  else if( m_defaultUnits == Unit::Mil )
321  {
322  switch( convertFrom )
323  {
324  case Unit::Inch :retval.value.dValue = 1.0 * 1000.0; break;
325  case Unit::Mil :retval.value.dValue = 1.0; break;
326  case Unit::Metric :retval.value.dValue = 1000.0 / 25.4; break;
327  case Unit::Invalid :break;
328  }
329  }
330  }
331  else if( isalpha( ch ))
332  {
333  // VAR
334  const char* cptr = &m_token.input[ m_token.pos ];
335  cptr++;
336 
337  while( isalnum( *cptr ))
338  cptr++;
340  retval.token = VAR;
341  size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
343  if( bytesToCopy >= sizeof( retval.value.text ))
344  bytesToCopy = sizeof( retval.value.text ) - 1;
345 
346  strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
347  retval.value.text[ bytesToCopy ] = 0;
349  }
350  else
351  {
352  // Single char tokens
353  switch( ch )
354  {
355  case '+' :retval.token = PLUS; break;
356  case '-' :retval.token = MINUS; break;
357  case '*' :retval.token = MULT; break;
358  case '/' :retval.token = DIVIDE; break;
359  case '(' :retval.token = PARENL; break;
360  case ')' :retval.token = PARENR; break;
361  case '=' :retval.token = ASSIGN; break;
362  case ';' :retval.token = SEMCOL; break;
363  default :m_parseError = true; break; /* invalid character */
364  }
365  m_token.pos++;
366  }
367 
368  return retval;
369 }
371 void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
372 {
373  m_varMap[ aString ] = aValue;
374 }
375 
376 double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
377 {
378  if( m_varMap[ aString ] )
379  return m_varMap[ aString ];
380  else
381  return 0.0;
382 }
std::map< wxString, double > m_varMap
#define ASSIGN
void SetVar(const wxString &aString, double aValue)
void Parse(void *yyp, int yymajor, ParseTOKENTYPE yyminor ParseARG_PDECL)
#define PARENR
struct NUMERIC_EVALUATOR::TokenStat m_token
#define MINUS
wxString OriginalText() const
#define SEMCOL
#define ENDS
#define PARENL
#define MULT
#define DIVIDE
void ParseFree(void *p, void(*freeProc)(void *))
#define UNIT
bool Process(const wxString &aString)
Definition: common.h:158
void parseSetResult(double)
void newString(const wxString &aString)
#define PLUS
size_t i
Definition: json11.cpp:597
void * ParseAlloc(void *(*mallocProc)(size_t))
NUMERIC_EVALUATOR(EDA_UNITS_T aUnits, bool aUseMils=false)
double GetVar(const wxString &aString)
#define VAR
#define VALUE
void parseError(const char *s)