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  snprintf( m_token.token, m_token.OutLen, "%.10g", val );
108 }
110 
112 {
114 }
117 bool NUMERIC_EVALUATOR::Process( const wxString& aString )
118 {
119  // Feed parser token after token until end of input.
121  newString( aString );
122  m_parseError = false;
124  Token tok;
125 
126  if( aString.IsEmpty() )
127  {
129  return true;
130  }
131 
132  do
133  {
134  tok = getToken();
135  numEval::Parse( m_parser, tok.token, tok.value, this );
137  if( m_parseFinished || tok.token == ENDS )
138  {
139  // Reset parser by passing zero as token ID, value is ignored.
140  numEval::Parse( m_parser, 0, tok.value, this );
141  break;
142  }
143  } while( tok.token );
145  return !m_parseError;
146 }
149 void NUMERIC_EVALUATOR::newString( const wxString& aString )
150 {
151  Clear();
152 
153  m_originalText = aString;
154 
155  m_token.token = reinterpret_cast<decltype( m_token.token )>( malloc( TokenStat::OutLen + 1 ) );
156  strcpy( m_token.token, "0" );
157  m_token.inputLen = aString.length();
159  m_token.input = aString.mb_str();
162 }
166 {
167  Token retval;
168  size_t idx;
169 
170  retval.token = ENDS;
171  retval.value.dValue = 0;
173  if( m_token.token == nullptr )
174  return retval;
176  if( m_token.input == nullptr )
177  return retval;
179  if( m_token.pos >= m_token.inputLen )
180  return retval;
181 
182  auto isDecimalSeparator = [ & ]( char ch ) -> bool {
183  return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
184  };
186  // Lambda: get value as string, store into clToken.token and update current index.
187  auto extractNumber = [ & ]() {
188  bool haveSeparator = false;
189  idx = 0;
190  auto ch = m_token.input[ m_token.pos ];
191 
192  do
193  {
194  if( isDecimalSeparator( ch ) && haveSeparator )
195  break;
196 
197  m_token.token[ idx++ ] = ch;
199  if( isDecimalSeparator( ch ))
200  haveSeparator = true;
201 
202  ch = m_token.input[ ++m_token.pos ];
203  } while( isdigit( ch ) || isDecimalSeparator( ch ));
204 
205  m_token.token[ idx ] = 0;
206 
207  // Ensure that the systems decimal separator is used
208  for( int i = strlen( m_token.token ); i; i-- )
209  if( isDecimalSeparator( m_token.token[ i - 1 ] ))
211  };
213  // Lamda: Get unit for current token.
214  // Valid units are ", in, mm, mil and thou. Returns Unit::Invalid otherwise.
215  auto checkUnit = [ this ]() -> Unit {
216  char ch = m_token.input[ m_token.pos ];
217 
218  if( ch == '"' )
219  {
220  m_token.pos++;
221  return Unit::Inch;
222  }
224  // Do not use strcasecmp() as it is not available on all platforms
225  const char* cptr = &m_token.input[ m_token.pos ];
226  const auto sizeLeft = m_token.inputLen - m_token.pos;
228  if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ))
229  {
230  m_token.pos += 2;
231  return Unit::Metric;
232  }
234  if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ))
235  {
236  m_token.pos += 2;
237  return Unit::Inch;
238  }
240  if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l' && !isalnum( cptr[ 3 ] ))
241  {
242  m_token.pos += 3;
243  return Unit::Mil;
244  }
245 
246  if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o' && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ))
247  {
248  m_token.pos += 4;
249  return Unit::Mil;
250  }
251 
252  return Unit::Invalid;
253  };
255  char ch;
256 
257  // Start processing of first/next token: Remove whitespace
258  for( ;; )
259  {
261 
262  if( ch == ' ' )
263  m_token.pos++;
264  else
265  break;
266  }
268  Unit convertFrom;
270  if( ch == 0 )
271  {
272  /* End of input */
273  }
274  else if( isdigit( ch ) || isDecimalSeparator( ch ))
275  {
276  // VALUE
277  extractNumber();
278  retval.token = VALUE;
279  retval.value.dValue = atof( m_token.token );
280  }
281  else if(( convertFrom = checkUnit()) != Unit::Invalid )
282  {
283  // UNIT
284  // Units are appended to a VALUE.
285  // Determine factor to default unit if unit for value is given.
286  // Example: Default is mm, unit is inch: factor is 25.4
287  // The factor is assigned to the terminal UNIT. The actual
288  // conversion is done within a parser action.
289  retval.token = UNIT;
291  {
292  switch( convertFrom )
293  {
294  case Unit::Inch :retval.value.dValue = 25.4; break;
295  case Unit::Mil :retval.value.dValue = 25.4 / 1000.0; break;
296  case Unit::Metric :retval.value.dValue = 1.0; break;
297  case Unit::Invalid :break;
298  }
299  }
300  else if( m_defaultUnits == Unit::Inch )
301  {
302  switch( convertFrom )
303  {
304  case Unit::Inch :retval.value.dValue = 1.0; break;
305  case Unit::Mil :retval.value.dValue = 1.0 / 1000.0; break;
306  case Unit::Metric :retval.value.dValue = 1.0 / 25.4; break;
307  case Unit::Invalid :break;
308  }
309  }
310  else if( m_defaultUnits == Unit::Mil )
311  {
312  switch( convertFrom )
313  {
314  case Unit::Inch :retval.value.dValue = 1.0 * 1000.0; break;
315  case Unit::Mil :retval.value.dValue = 1.0; break;
316  case Unit::Metric :retval.value.dValue = 1000.0 / 25.4; break;
317  case Unit::Invalid :break;
318  }
319  }
320  }
321  else if( isalpha( ch ))
322  {
323  // VAR
324  const char* cptr = &m_token.input[ m_token.pos ];
325  cptr++;
326 
327  while( isalnum( *cptr ))
328  cptr++;
330  retval.token = VAR;
331  size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
332 
333  if( bytesToCopy >= sizeof( retval.value.text ))
334  bytesToCopy = sizeof( retval.value.text ) - 1;
335 
336  strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
337  retval.value.text[ bytesToCopy ] = 0;
338  m_token.pos += cptr - &m_token.input[ m_token.pos ];
339  }
340  else
341  {
342  // Single char tokens
343  switch( ch )
344  {
345  case '+' :retval.token = PLUS; break;
346  case '-' :retval.token = MINUS; break;
347  case '*' :retval.token = MULT; break;
348  case '/' :retval.token = DIVIDE; break;
349  case '(' :retval.token = PARENL; break;
350  case ')' :retval.token = PARENR; break;
351  case '=' :retval.token = ASSIGN; break;
352  case ';' :retval.token = SEMCOL; break;
353  default :m_parseError = true; break; /* invalid character */
354  }
355  m_token.pos++;
356  }
358  return retval;
359 }
360 
361 void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
362 {
363  m_varMap[ aString ] = aValue;
364 }
365 
366 double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
367 {
368  if( m_varMap[ aString ] )
369  return m_varMap[ aString ];
370  else
371  return 0.0;
372 }
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:161
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)