KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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, [email protected]
5 * Copyright The 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
21#include <string_utils.h>
23
24/* The (generated) lemon parser is written in C.
25 * In order to keep its symbol from the global namespace include the parser code with
26 * a C++ namespace.
27 */
28namespace numEval
29{
30
31#ifdef __GNUC__
32#pragma GCC diagnostic push
33#pragma GCC diagnostic ignored "-Wunused-variable"
34#pragma GCC diagnostic ignored "-Wsign-compare"
35#endif
36
37#include <libeval/grammar.c>
38#include <libeval/grammar.h>
39
40#ifdef __GNUC__
41#pragma GCC diagnostic pop
42#endif
43
44} /* namespace numEval */
45
46
48{
50
51 m_parseError = false;
52 m_parseFinished = false;
53
54 m_parser = numEval::ParseAlloc( malloc );
55
56 SetDefaultUnits( aUnits );
57}
58
59
61{
62 numEval::ParseFree( m_parser, free );
63
64 // Allow explicit call to destructor
65 m_parser = nullptr;
66
67 Clear();
68}
69
70
72{
73 delete[] m_token.token;
74 m_token.token = nullptr;
75 m_token.input = nullptr;
76 m_parseError = true;
77 m_originalText = wxEmptyString;
78}
79
80
82{
83 switch( aUnits )
84 {
85 case EDA_UNITS::MILLIMETRES: m_defaultUnits = Unit::MM; break;
86 case EDA_UNITS::MILS: m_defaultUnits = Unit::Mil; break;
87 case EDA_UNITS::INCHES: m_defaultUnits = Unit::Inch; break;
88 case EDA_UNITS::DEGREES: m_defaultUnits = Unit::Degrees; break;
89 case EDA_UNITS::UNSCALED: m_defaultUnits = Unit::SI; break;
90 default: m_defaultUnits = Unit::MM; break;
91 }
92}
93
94
96{
97 struct lconv* lc = localeconv();
98 m_localeDecimalSeparator = *lc->decimal_point;
99}
100
101
102// NOT UNUSED. Called by LEMON grammar.
103void NUMERIC_EVALUATOR::parseError( const char* s )
104{
105 m_parseError = true;
106}
107
108
109// NOT UNUSED. Called by LEMON grammar.
111{
112 m_parseFinished = true;
113}
114
115
116// NOT UNUSED. Called by LEMON grammar.
118{
119 if( std::isnan( val ) )
120 {
121 // Naively printing this with %g produces "nan" on some platforms
122 // and "-nan(ind)" on others (e.g. MSVC). So force a "standard" string.
123 snprintf( m_token.token, m_token.outputLen, "%s", "NaN" );
124 }
125 else
126 {
127 // Can be printed as a floating point
128 // Warning: DO NOT use a format like %f or %g, because they can create issues.
129 // especially %g can generate an exponent, incompatible with UNIT_BINDER
130 // Use the optimized UIDouble2Str
131 snprintf( m_token.token, m_token.outputLen, "%s", UIDouble2Str( val ).c_str() );
132 }
133}
134
135
137{
138 return m_originalText;
139}
140
141
142bool NUMERIC_EVALUATOR::Process( const wxString& aString )
143{
144 // Feed parser token after token until end of input.
145
146 newString( aString );
147 m_parseError = false;
148 m_parseFinished = false;
149 Token tok;
150
151 if( aString.IsEmpty() )
152 {
153 m_parseFinished = true;
154 return true;
155 }
156
157 do
158 {
159 tok = getToken();
160 numEval::Parse( m_parser, tok.token, tok.value, this );
161
162 if( m_parseFinished || tok.token == ENDS )
163 {
164 // Reset parser by passing zero as token ID, value is ignored.
165 numEval::Parse( m_parser, 0, tok.value, this );
166 break;
167 }
168 } while( tok.token );
169
170 return !m_parseError;
171}
172
173
174void NUMERIC_EVALUATOR::newString( const wxString& aString )
175{
176 Clear();
177
178 m_originalText = aString;
179 m_token.input = aString.utf8_str();
180 m_token.inputLen = strlen( m_token.input );
181 m_token.outputLen = std::max<std::size_t>( 64, m_token.inputLen + 1 );
182 m_token.pos = 0;
183 m_token.token = new char[m_token.outputLen]();
184 m_token.token[0] = '0';
185
186 m_parseFinished = false;
187}
188
189
191{
192 Token retval;
193 size_t idx;
194
195 retval.token = ENDS;
196 retval.value.dValue = 0;
197 retval.value.valid = false;
198 retval.value.text[0] = 0;
199
200 if( m_token.token == nullptr )
201 return retval;
202
203 if( m_token.input == nullptr )
204 return retval;
205
206 if( m_token.pos >= m_token.inputLen )
207 return retval;
208
209 // Support for old school decimal separators (ie: "2K2")
210 auto isOldSchoolDecimalSeparator =
211 []( char ch, double* siScaler ) -> bool
212 {
213 switch( ch )
214 {
215 case 'a': *siScaler = 1.0e-18; return true;
216 case 'f': *siScaler = 1.0e-15; return true;
217 case 'p': *siScaler = 1.0e-12; return true;
218 case 'n': *siScaler = 1.0e-9; return true;
219 case 'u': *siScaler = 1.0e-6; return true;
220 case 'm': *siScaler = 1.0e-3; return true;
221 case 'k':
222 case 'K': *siScaler = 1.0e3; return true;
223 case 'M': *siScaler = 1.0e6; return true;
224 case 'G': *siScaler = 1.0e9; return true;
225 case 'T': *siScaler = 1.0e12; return true;
226 case 'P': *siScaler = 1.0e15; return true;
227 case 'E': *siScaler = 1.0e18; return true;
228 default: return false;
229 }
230 };
231
232 auto isDecimalSeparator =
233 [&]( char ch ) -> bool
234 {
235 double dummy;
236
237 if( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' )
238 return true;
239
240 if( m_defaultUnits == Unit::SI && isOldSchoolDecimalSeparator( ch, &dummy ) )
241 return true;
242
243 return false;
244 };
245
246 // Lambda: get value as string, store into clToken.token and update current index.
247 auto extractNumber =
248 [&]( double* aScaler )
249 {
250 bool haveSeparator = false;
251 double siScaler = 1.0;
252 char ch = m_token.input[ m_token.pos ];
253
254 idx = 0;
255
256 do
257 {
258 if( isDecimalSeparator( ch ) )
259 {
260 if( haveSeparator )
261 break;
262 else
263 haveSeparator = true;
264
265 if( isOldSchoolDecimalSeparator( ch, &siScaler ) )
266 *aScaler = siScaler;
267
269 }
270 else
271 {
272 m_token.token[ idx++ ] = ch;
273 }
274
275 ch = m_token.input[++m_token.pos];
276
277 // the below static cast is to avoid partial unicode chars triggering an
278 // assert in isdigit on msvc
279 } while( isdigit( static_cast<unsigned char>( ch ) ) || isDecimalSeparator( ch ) );
280
281 m_token.token[ idx ] = 0;
282 };
283
284 // Lamda: Get unit for current token.
285 // Valid units are ", in, um, cm, mm, mil and thou. Returns Unit::Invalid otherwise.
286 auto checkUnit =
287 [&]( double* siScaler ) -> Unit
288 {
289 char ch = m_token.input[ m_token.pos ];
290
291 if( ch == '"' )
292 {
293 m_token.pos++;
294 return Unit::Inch;
295 }
296
297 // Do not use strcasecmp() as it is not available on all platforms
298 const char* cptr = &m_token.input[ m_token.pos ];
299 const auto sizeLeft = m_token.inputLen - m_token.pos;
300
301 // We should really give this unicode support
302 if( sizeLeft >= 2 && ch == '\xC2' && cptr[1] == '\xB0' )
303 {
304 m_token.pos += 2;
305 return Unit::Degrees;
306 }
307
308 // Ideally we should also handle the unicode characters that can be used for micro,
309 // but unicode handling in this tokenizer doesn't work.
310 // (e.g. add support for μm (µ is MICRO SIGN), µm (µ is GREEK SMALL LETTER MU)
311 // later)
312 if( sizeLeft >= 2 && ch == 'u' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
313 {
314 m_token.pos += 2;
315 return Unit::UM;
316 }
317
318 if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
319 {
320 m_token.pos += 2;
321 return Unit::MM;
322 }
323
324 if( sizeLeft >= 2 && ch == 'c' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
325 {
326 m_token.pos += 2;
327 return Unit::CM;
328 }
329
330 if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ) )
331 {
332 m_token.pos += 2;
333 return Unit::Inch;
334 }
335
336 if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l'
337 && !isalnum( cptr[ 3 ] ) )
338 {
339 m_token.pos += 3;
340 return Unit::Mil;
341 }
342
343 if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o'
344 && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ) )
345 {
346 m_token.pos += 4;
347 return Unit::Mil;
348 }
349
350 if( m_defaultUnits == Unit::SI && sizeLeft >= 1
351 && isOldSchoolDecimalSeparator( ch, siScaler ) )
352 {
353 m_token.pos++;
354 return Unit::SI;
355 }
356
357 return Unit::Invalid;
358 };
359
360 char ch;
361
362 // Start processing of first/next token: Remove whitespace
363 for( ;; )
364 {
365 ch = m_token.input[ m_token.pos ];
366
367 if( ch == ' ' )
368 m_token.pos++;
369 else
370 break;
371 }
372
373 double siScaler = 1.0;
374 Unit convertFrom = Unit::Invalid;
375
376 if( ch == 0 )
377 {
378 /* End of input */
379 }
380 else if( isdigit( static_cast<unsigned char>( ch ) ) || isDecimalSeparator( ch ) )
381 {
382 // the above static cast is to avoid partial unicode chars triggering an assert in
383 // isdigit on msvc
384 // VALUE
385 extractNumber( &siScaler );
386 retval.token = VALUE;
387 retval.value.dValue = atof( m_token.token ) * siScaler;
388 }
389 else if( ( convertFrom = checkUnit( &siScaler ) ) != Unit::Invalid )
390 {
391 // UNIT
392 // Units are appended to a VALUE.
393 // Determine factor to default unit if unit for value is given.
394 // Example: Default is mm, unit is inch: factor is 25.4
395 // The factor is assigned to the terminal UNIT. The actual
396 // conversion is done within a parser action.
397 retval.token = UNIT;
398
399 if( m_defaultUnits == Unit::MM )
400 {
401 switch( convertFrom )
402 {
403 case Unit::Inch: retval.value.dValue = 25.4; break;
404 case Unit::Mil: retval.value.dValue = 25.4 / 1000.0; break;
405 case Unit::UM: retval.value.dValue = 1 / 1000.0; break;
406 case Unit::MM: retval.value.dValue = 1.0; break;
407 case Unit::CM: retval.value.dValue = 10.0; break;
408 default:
409 case Unit::Invalid: break;
410 }
411 }
412 else if( m_defaultUnits == Unit::Inch )
413 {
414 switch( convertFrom )
415 {
416 case Unit::Inch: retval.value.dValue = 1.0; break;
417 case Unit::Mil: retval.value.dValue = 1.0 / 1000.0; break;
418 case Unit::UM: retval.value.dValue = 1.0 / 25400.0; break;
419 case Unit::MM: retval.value.dValue = 1.0 / 25.4; break;
420 case Unit::CM: retval.value.dValue = 1.0 / 2.54; break;
421 default:
422 case Unit::Invalid: break;
423 }
424 }
425 else if( m_defaultUnits == Unit::Mil )
426 {
427 switch( convertFrom )
428 {
429 case Unit::Inch: retval.value.dValue = 1.0 * 1000.0; break;
430 case Unit::Mil: retval.value.dValue = 1.0; break;
431 case Unit::UM: retval.value.dValue = 1.0 / 25.4; break;
432 case Unit::MM: retval.value.dValue = 1000.0 / 25.4; break;
433 case Unit::CM: retval.value.dValue = 1000.0 / 2.54; break;
434 default:
435 case Unit::Invalid: break;
436 }
437 }
438 else if( m_defaultUnits == Unit::Degrees && convertFrom == Unit::Degrees )
439 {
440 retval.value.dValue = 1.0;
441 }
442 else if( m_defaultUnits == Unit::SI )
443 {
444 retval.value.dValue = siScaler;
445 }
446 }
447 else if( isalpha( ch ) )
448 {
449 // VAR
450 const char* cptr = &m_token.input[ m_token.pos ];
451 cptr++;
452
453 while( isalnum( *cptr ) )
454 cptr++;
455
456 retval.token = VAR;
457 size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
458
459 if( bytesToCopy >= sizeof( retval.value.text ) )
460 bytesToCopy = sizeof( retval.value.text ) - 1;
461
462 strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
463 retval.value.text[ bytesToCopy ] = 0;
464 m_token.pos += cptr - &m_token.input[ m_token.pos ];
465 }
466 else
467 {
468 // Single char tokens
469 switch( ch )
470 {
471 case '+': retval.token = PLUS; break;
472 case '-': retval.token = MINUS; break;
473 case '*': retval.token = MULT; break;
474 case '/': retval.token = DIVIDE; break;
475 case '(': retval.token = PARENL; break;
476 case ')': retval.token = PARENR; break;
477 case '=': retval.token = ASSIGN; break;
478 case ';': retval.token = SEMCOL; break;
479 default: m_parseError = true; break; /* invalid character */
480 }
481
482 m_token.pos++;
483 }
484
485 if( !m_parseError )
486 retval.value.valid = true;
487
488 return retval;
489}
490
491
492void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
493{
494 m_varMap[ aString ] = aValue;
495}
496
497
498double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
499{
500 auto it = m_varMap.find( aString );
501
502 if( it != m_varMap.end() )
503 {
504 return it->second;
505 }
506 else
507 {
508 m_parseError = true;
509 return 0.0;
510 }
511}
void parseSetResult(double)
wxString OriginalText() const
struct NUMERIC_EVALUATOR::TokenStat m_token
void newString(const wxString &aString)
void parseError(const char *s)
double GetVar(const wxString &aString)
NUMERIC_EVALUATOR(EDA_UNITS aUnits)
void SetDefaultUnits(EDA_UNITS aUnits)
void SetVar(const wxString &aString, double aValue)
bool Process(const wxString &aString)
std::map< wxString, double > m_varMap
EDA_UNITS
Definition: eda_units.h:46
std::vector< FAB_LAYER_COLOR > dummy
std::string UIDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 We want to avoid scientific ...
numEval::TokenType value