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 <libeval/grammar.c>
37 #include <libeval/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 
54  m_parser = numEval::ParseAlloc( malloc );
55 
56  switch( aUnits )
57  {
58  case EDA_UNITS::INCHES:
59  if( aUseMils )
61  else
63  break;
66  break;
67  default:m_defaultUnits = Unit::MM;
68  break;
69  }
70 }
71 
72 
74 {
75  numEval::ParseFree( m_parser, free );
76 
77  // Allow explicit call to destructor
78  m_parser = nullptr;
79 
80  Clear();
81 }
82 
83 
85 {
86  free( m_token.token );
87  m_token.token = nullptr;
88  m_token.input = nullptr;
89  m_parseError = true;
90  m_originalText = wxEmptyString;
91 }
92 
93 
94 void NUMERIC_EVALUATOR::parseError( const char* s )
95 {
96  m_parseError = true;
97 }
98 
99 
101 {
102  m_parseFinished = true;
103 }
104 
105 
107 {
108  if( std::isnan( val ) )
109  {
110  // Naively printing this with %g produces "nan" on some platforms
111  // and "-nan(ind)" on others (e.g. MSVC). So force a "standard" string.
112  snprintf( m_token.token, m_token.OutLen, "%s", "NaN" );
113  }
114  else
115  {
116  // Can be printed as a floating point
117  snprintf( m_token.token, m_token.OutLen, "%.10g", val );
118  }
119 }
120 
121 
123 {
124  return m_originalText;
125 }
126 
127 
128 bool NUMERIC_EVALUATOR::Process( const wxString& aString )
129 {
130  // Feed parser token after token until end of input.
131 
132  newString( aString );
133  m_parseError = false;
134  m_parseFinished = false;
135  Token tok;
136 
137  if( aString.IsEmpty() )
138  {
139  m_parseFinished = true;
140  return true;
141  }
142 
143  do
144  {
145  tok = getToken();
146  numEval::Parse( m_parser, tok.token, tok.value, this );
147 
148  if( m_parseFinished || tok.token == ENDS )
149  {
150  // Reset parser by passing zero as token ID, value is ignored.
151  numEval::Parse( m_parser, 0, tok.value, this );
152  break;
153  }
154  } while( tok.token );
155 
156  return !m_parseError;
157 }
158 
159 
160 void NUMERIC_EVALUATOR::newString( const wxString& aString )
161 {
162  Clear();
163 
164  m_originalText = aString;
165 
166  m_token.token = reinterpret_cast<decltype( m_token.token )>( malloc( TokenStat::OutLen + 1 ) );
167  strcpy( m_token.token, "0" );
168  m_token.inputLen = aString.length();
169  m_token.pos = 0;
170  m_token.input = aString.mb_str();
171 
172  m_parseFinished = false;
173 }
174 
175 
177 {
178  Token retval;
179  size_t idx;
180 
181  retval.token = ENDS;
182  retval.value.dValue = 0;
183 
184  if( m_token.token == nullptr )
185  return retval;
186 
187  if( m_token.input == nullptr )
188  return retval;
189 
190  if( m_token.pos >= m_token.inputLen )
191  return retval;
192 
193  auto isDecimalSeparator = [ & ]( char ch ) -> bool {
194  return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
195  };
196 
197  // Lambda: get value as string, store into clToken.token and update current index.
198  auto extractNumber = [ & ]() {
199  bool haveSeparator = false;
200  idx = 0;
201  auto ch = m_token.input[ m_token.pos ];
202 
203  do
204  {
205  if( isDecimalSeparator( ch ) && haveSeparator )
206  break;
207 
208  m_token.token[ idx++ ] = ch;
209 
210  if( isDecimalSeparator( ch ))
211  haveSeparator = true;
212 
213  ch = m_token.input[ ++m_token.pos ];
214  } while( isdigit( ch ) || isDecimalSeparator( ch ));
215 
216  m_token.token[ idx ] = 0;
217 
218  // Ensure that the systems decimal separator is used
219  for( int i = strlen( m_token.token ); i; i-- )
220  if( isDecimalSeparator( m_token.token[ i - 1 ] ))
222  };
223 
224  // Lamda: Get unit for current token.
225  // Valid units are ", in, mm, mil and thou. Returns Unit::Invalid otherwise.
226  auto checkUnit = [ this ]() -> Unit {
227  char ch = m_token.input[ m_token.pos ];
228 
229  if( ch == '"' )
230  {
231  m_token.pos++;
232  return Unit::Inch;
233  }
234 
235  // Do not use strcasecmp() as it is not available on all platforms
236  const char* cptr = &m_token.input[ m_token.pos ];
237  const auto sizeLeft = m_token.inputLen - m_token.pos;
238 
239  if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ))
240  {
241  m_token.pos += 2;
242  return Unit::MM;
243  }
244 
245  if( sizeLeft >= 2 && ch == 'c' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ))
246  {
247  m_token.pos += 2;
248  return Unit::CM;
249  }
250 
251  if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ))
252  {
253  m_token.pos += 2;
254  return Unit::Inch;
255  }
256 
257  if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l' && !isalnum( cptr[ 3 ] ))
258  {
259  m_token.pos += 3;
260  return Unit::Mil;
261  }
262 
263  if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o' && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ))
264  {
265  m_token.pos += 4;
266  return Unit::Mil;
267  }
268 
269  return Unit::Invalid;
270  };
271 
272  char ch;
273 
274  // Start processing of first/next token: Remove whitespace
275  for( ;; )
276  {
277  ch = m_token.input[ m_token.pos ];
278 
279  if( ch == ' ' )
280  m_token.pos++;
281  else
282  break;
283  }
284 
285  Unit convertFrom;
286 
287  if( ch == 0 )
288  {
289  /* End of input */
290  }
291  else if( isdigit( ch ) || isDecimalSeparator( ch ))
292  {
293  // VALUE
294  extractNumber();
295  retval.token = VALUE;
296  retval.value.dValue = atof( m_token.token );
297  }
298  else if(( convertFrom = checkUnit()) != Unit::Invalid )
299  {
300  // UNIT
301  // Units are appended to a VALUE.
302  // Determine factor to default unit if unit for value is given.
303  // Example: Default is mm, unit is inch: factor is 25.4
304  // The factor is assigned to the terminal UNIT. The actual
305  // conversion is done within a parser action.
306  retval.token = UNIT;
307  if( m_defaultUnits == Unit::MM )
308  {
309  switch( convertFrom )
310  {
311  case Unit::Inch :retval.value.dValue = 25.4; break;
312  case Unit::Mil :retval.value.dValue = 25.4 / 1000.0; break;
313  case Unit::MM :retval.value.dValue = 1.0; break;
314  case Unit::CM :retval.value.dValue = 10.0; break;
315  case Unit::Invalid :break;
316  }
317  }
318  else if( m_defaultUnits == Unit::Inch )
319  {
320  switch( convertFrom )
321  {
322  case Unit::Inch :retval.value.dValue = 1.0; break;
323  case Unit::Mil :retval.value.dValue = 1.0 / 1000.0; break;
324  case Unit::MM :retval.value.dValue = 1.0 / 25.4; break;
325  case Unit::CM :retval.value.dValue = 1.0 / 2.54; break;
326  case Unit::Invalid :break;
327  }
328  }
329  else if( m_defaultUnits == Unit::Mil )
330  {
331  switch( convertFrom )
332  {
333  case Unit::Inch :retval.value.dValue = 1.0 * 1000.0; break;
334  case Unit::Mil :retval.value.dValue = 1.0; break;
335  case Unit::MM :retval.value.dValue = 1000.0 / 25.4; break;
336  case Unit::CM :retval.value.dValue = 1000.0 / 2.54; break;
337  case Unit::Invalid :break;
338  }
339  }
340  }
341  else if( isalpha( ch ))
342  {
343  // VAR
344  const char* cptr = &m_token.input[ m_token.pos ];
345  cptr++;
346 
347  while( isalnum( *cptr ))
348  cptr++;
349 
350  retval.token = VAR;
351  size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
352 
353  if( bytesToCopy >= sizeof( retval.value.text ))
354  bytesToCopy = sizeof( retval.value.text ) - 1;
355 
356  strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
357  retval.value.text[ bytesToCopy ] = 0;
358  m_token.pos += cptr - &m_token.input[ m_token.pos ];
359  }
360  else
361  {
362  // Single char tokens
363  switch( ch )
364  {
365  case '+' :retval.token = PLUS; break;
366  case '-' :retval.token = MINUS; break;
367  case '*' :retval.token = MULT; break;
368  case '/' :retval.token = DIVIDE; break;
369  case '(' :retval.token = PARENL; break;
370  case ')' :retval.token = PARENR; break;
371  case '=' :retval.token = ASSIGN; break;
372  case ';' :retval.token = SEMCOL; break;
373  default :m_parseError = true; break; /* invalid character */
374  }
375  m_token.pos++;
376  }
377 
378  return retval;
379 }
380 
381 void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
382 {
383  m_varMap[ aString ] = aValue;
384 }
385 
386 double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
387 {
388  if( m_varMap[ aString ] )
389  return m_varMap[ aString ];
390  else
391  return 0.0;
392 }
EDA_UNITS
Definition: common.h:198
std::map< wxString, double > m_varMap
void SetVar(const wxString &aString, double aValue)
struct NUMERIC_EVALUATOR::TokenStat m_token
wxString OriginalText() const
numEval::TokenType value
bool Process(const wxString &aString)
void parseSetResult(double)
void newString(const wxString &aString)
Field Value of part, i.e. "3.3K".
NUMERIC_EVALUATOR(EDA_UNITS aUnits, bool aUseMils=false)
double GetVar(const wxString &aString)
void parseError(const char *s)