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
98
99
101{
102 struct lconv* lc = localeconv();
103 m_localeDecimalSeparator = *lc->decimal_point;
104}
105
106
107// NOT UNUSED. Called by LEMON grammar.
108void NUMERIC_EVALUATOR::parseError( const char* s )
109{
110 m_parseError = true;
111}
112
113
114// NOT UNUSED. Called by LEMON grammar.
116{
117 m_parseFinished = true;
118}
119
120
121// NOT UNUSED. Called by LEMON grammar.
123{
124 if( std::isnan( val ) )
125 {
126 // Naively printing this with %g produces "nan" on some platforms
127 // and "-nan(ind)" on others (e.g. MSVC). So force a "standard" string.
128 snprintf( m_token.token, m_token.outputLen, "%s", "NaN" );
129 }
130 else
131 {
132 // Can be printed as a floating point
133 // Warning: DO NOT use a format like %f or %g, because they can create issues.
134 // especially %g can generate an exponent, incompatible with UNIT_BINDER
135 // Use the optimized UIDouble2Str
136 snprintf( m_token.token, m_token.outputLen, "%s", UIDouble2Str( val ).c_str() );
137 }
138}
139
140
142{
143 return m_originalText;
144}
145
146
147bool NUMERIC_EVALUATOR::Process( const wxString& aString )
148{
149 // Feed parser token after token until end of input.
150
151 newString( aString );
152 m_parseError = false;
153 m_parseFinished = false;
154 Token tok;
155
156 if( aString.IsEmpty() )
157 {
158 m_parseFinished = true;
159 return true;
160 }
161
162 do
163 {
164 tok = getToken();
165 numEval::Parse( m_parser, tok.token, tok.value, this );
166
167 if( m_parseFinished || tok.token == ENDS )
168 {
169 // Reset parser by passing zero as token ID, value is ignored.
170 numEval::Parse( m_parser, 0, tok.value, this );
171 break;
172 }
173 } while( tok.token );
174
175 return !m_parseError;
176}
177
178
179void NUMERIC_EVALUATOR::newString( const wxString& aString )
180{
181 Clear();
182
183 m_originalText = aString;
184 m_token.input = aString.utf8_str();
185 m_token.inputLen = strlen( m_token.input );
186 m_token.outputLen = std::max<std::size_t>( 64, m_token.inputLen + 1 );
187 m_token.pos = 0;
188 m_token.token = new char[m_token.outputLen]();
189 m_token.token[0] = '0';
190
191 m_parseFinished = false;
192}
193
194
195// Support for old school decimal separators (ie: "2K2") according to IEC 60062
196bool NUMERIC_EVALUATOR::IsOldSchoolDecimalSeparator( wxUniChar ch, double* siScaler )
197{
198 if( ch == 'p' ) { *siScaler = 1.0e-12; return true; }
199 else if( ch == 'n' ) { *siScaler = 1.0e-9; return true; }
200 else if( ch == wxT( "µ" )[0] ) { *siScaler = 1.0e-6; return true; }
201 else if( ch == wxT( "μ" )[0] ) { *siScaler = 1.0e-6; return true; }
202 else if( ch == 'u' ) { *siScaler = 1.0e-6; return true; }
203 else if( ch == 'm' ) { *siScaler = 1.0e-3; return true; }
204 else if( ch == 'L' ) { *siScaler = 1.0e-3; return true; }
205 else if( ch == 'R' ) { *siScaler = 1.0; return true; }
206 else if( ch == 'F' ) { *siScaler = 1.0; return true; }
207 else if( ch == 'k' ) { *siScaler = 1.0e3; return true; }
208 else if( ch == 'K' ) { *siScaler = 1.0e3; return true; }
209 else if( ch == 'M' ) { *siScaler = 1.0e6; return true; }
210 else if( ch == 'G' ) { *siScaler = 1.0e9; return true; }
211 else if( ch == 'T' ) { *siScaler = 1.0e12; return true; }
212 else return false;
213};
214
215
216// Limited version of above for 8-bit chars
217bool NUMERIC_EVALUATOR::isOldSchoolDecimalSeparator( char ch, double* siScaler )
218{
219 switch( ch )
220 {
221 case 'p': *siScaler = 1.0e-12; return true;
222 case 'n': *siScaler = 1.0e-9; return true;
223 case 'u': *siScaler = 1.0e-6; return true;
224 case 'm': *siScaler = 1.0e-3; return true;
225 case 'L': *siScaler = 1.0e-3; return true;
226 case 'R': *siScaler = 1.0; return true;
227 case 'F': *siScaler = 1.0; return true;
228 case 'k': *siScaler = 1.0e3; return true;
229 case 'K': *siScaler = 1.0e3; return true;
230 case 'M': *siScaler = 1.0e6; return true;
231 case 'G': *siScaler = 1.0e9; return true;
232 case 'T': *siScaler = 1.0e12; return true;
233 default: return false;
234 }
235};
236
237
238bool NUMERIC_EVALUATOR::IsDecimalSeparator( char ch, char localeSeparator, bool allowInfixNotation )
239{
240 double dummy;
241
242 if( ch == localeSeparator || ch == '.' || ch == ',' )
243 return true;
244
245 if( allowInfixNotation && IsOldSchoolDecimalSeparator( ch, &dummy ) )
246 return true;
247
248 return false;
249};
250
251
253{
254 // the below static cast is to avoid partial unicode chars triggering an assert in isdigit on msvc
255 return isdigit( static_cast<unsigned char>( ch ) );
256}
257
258
260{
261 bool allowInfix = m_defaultUnits == Unit::SI;
262
263 Token retval;
264 retval.token = ENDS;
265 retval.value.dValue = 0;
266 retval.value.valid = false;
267 retval.value.text[0] = 0;
268
269 if( m_token.token == nullptr )
270 return retval;
271
272 if( m_token.input == nullptr )
273 return retval;
274
275 if( m_token.pos >= m_token.inputLen )
276 return retval;
277
278 // Lambda: get value as string, store into clToken.token and update current index.
279 auto extractNumber =
280 [&]( double* aScaler )
281 {
282 bool haveSeparator = false;
283 double siScaler = 1.0;
284 char ch = m_token.input[ m_token.pos ];
285
286 size_t idx = 0;
287
288 do
289 {
290 if( IsDecimalSeparator( ch, m_localeDecimalSeparator, allowInfix ) )
291 {
292 if( haveSeparator )
293 break;
294 else
295 haveSeparator = true;
296
297 if( IsOldSchoolDecimalSeparator( ch, &siScaler ) )
298 *aScaler = siScaler;
299
300 m_token.token[ idx++ ] = m_localeDecimalSeparator;
301 }
302 else
303 {
304 m_token.token[ idx++ ] = ch;
305 }
306
307 ch = m_token.input[++m_token.pos];
308
309 } while( IsDigit( ch ) || IsDecimalSeparator( ch, m_localeDecimalSeparator, allowInfix ) );
310
311 m_token.token[ idx ] = 0;
312 };
313
314 // Lamda: Get unit for current token.
315 // Valid units are ", in, um, cm, mm, mil, ps and thou. Returns Unit::Invalid otherwise.
316 auto checkUnit =
317 [&]( double* siScaler ) -> Unit
318 {
319 char ch = m_token.input[ m_token.pos ];
320
321 if( ch == '"' )
322 {
323 m_token.pos++;
324 return Unit::Inch;
325 }
326
327 // Do not use strcasecmp() as it is not available on all platforms
328 const char* cptr = &m_token.input[ m_token.pos ];
329 const auto sizeLeft = m_token.inputLen - m_token.pos;
330
331 // We should really give this unicode support
332 if( sizeLeft >= 2 && ch == '\xC2' && cptr[1] == '\xB0' )
333 {
334 m_token.pos += 2;
335 return Unit::Degrees;
336 }
337
338 // Ideally we should also handle the unicode characters that can be used for micro,
339 // but unicode handling in this tokenizer doesn't work.
340 // (e.g. add support for μm (µ is MICRO SIGN), µm (µ is GREEK SMALL LETTER MU)
341 // later)
342 if( sizeLeft >= 2 && ch == 'u' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
343 {
344 m_token.pos += 2;
345 return Unit::UM;
346 }
347
348 if( sizeLeft >= 5 && ch == 'p' && cptr[1] == 's' && cptr[2] == '/' && cptr[3] == 'i'
349 && cptr[4] == 'n' && !isalnum( cptr[5] ) )
350 {
351 m_token.pos += 5;
352 return Unit::PsPerInch;
353 }
354
355 if( sizeLeft >= 5 && ch == 'p' && cptr[1] == 's' && cptr[2] == '/' && cptr[3] == 'c'
356 && cptr[4] == 'm' && !isalnum( cptr[5] ) )
357 {
358 m_token.pos += 5;
359 return Unit::PsPerCm;
360 }
361
362 if( sizeLeft >= 5 && ch == 'p' && cptr[1] == 's' && cptr[2] == '/' && cptr[3] == 'm'
363 && cptr[4] == 'm' && !isalnum( cptr[5] ) )
364 {
365 m_token.pos += 5;
366 return Unit::PsPerMm;
367 }
368
369 if( sizeLeft >= 2 && ch == 'f' && cptr[1] == 's' && !isalnum( cptr[2] ) )
370 {
371 m_token.pos += 2;
372 return Unit::Femtoseconds;
373 }
374
375 if( sizeLeft >= 2 && ch == 'p' && cptr[1] == 's' && !isalnum( cptr[2] ) )
376 {
377 m_token.pos += 2;
378 return Unit::Picoseconds;
379 }
380
381 if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
382 {
383 m_token.pos += 2;
384 return Unit::MM;
385 }
386
387 if( sizeLeft >= 2 && ch == 'c' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
388 {
389 m_token.pos += 2;
390 return Unit::CM;
391 }
392
393 if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ) )
394 {
395 m_token.pos += 2;
396 return Unit::Inch;
397 }
398
399 if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l'
400 && !isalnum( cptr[ 3 ] ) )
401 {
402 m_token.pos += 3;
403 return Unit::Mil;
404 }
405
406 if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o'
407 && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ) )
408 {
409 m_token.pos += 4;
410 return Unit::Mil;
411 }
412
413 if( allowInfix && sizeLeft >= 1 && IsOldSchoolDecimalSeparator( ch, siScaler ) )
414 {
415 m_token.pos++;
416 return Unit::SI;
417 }
418
419 return Unit::Invalid;
420 };
421
422 char ch;
423
424 // Start processing of first/next token: Remove whitespace
425 for( ;; )
426 {
427 ch = m_token.input[ m_token.pos ];
428
429 if( ch == ' ' )
430 m_token.pos++;
431 else
432 break;
433 }
434
435 double siScaler = 1.0;
436 Unit convertFrom = Unit::Invalid;
437
438 if( ch == 0 )
439 {
440 /* End of input */
441 }
442 else if( IsDigit( ch ) || IsDecimalSeparator( ch, m_localeDecimalSeparator, allowInfix ) )
443 {
444 // VALUE
445 extractNumber( &siScaler );
446 retval.token = VALUE;
447 retval.value.dValue = atof( m_token.token ) * siScaler;
448 }
449 else if( ( convertFrom = checkUnit( &siScaler ) ) != Unit::Invalid )
450 {
451 // UNIT
452 // Units are appended to a VALUE.
453 // Determine factor to default unit if unit for value is given.
454 // Example: Default is mm, unit is inch: factor is 25.4
455 // The factor is assigned to the terminal UNIT. The actual
456 // conversion is done within a parser action.
457 retval.token = UNIT;
458
459 if( m_defaultUnits == Unit::MM )
460 {
461 switch( convertFrom )
462 {
463 case Unit::Inch: retval.value.dValue = 25.4; break;
464 case Unit::Mil: retval.value.dValue = 25.4 / 1000.0; break;
465 case Unit::UM: retval.value.dValue = 1 / 1000.0; break;
466 case Unit::MM: retval.value.dValue = 1.0; break;
467 case Unit::CM: retval.value.dValue = 10.0; break;
468 default:
469 case Unit::Invalid: break;
470 }
471 }
472 else if( m_defaultUnits == Unit::Inch )
473 {
474 switch( convertFrom )
475 {
476 case Unit::Inch: retval.value.dValue = 1.0; break;
477 case Unit::Mil: retval.value.dValue = 1.0 / 1000.0; break;
478 case Unit::UM: retval.value.dValue = 1.0 / 25400.0; break;
479 case Unit::MM: retval.value.dValue = 1.0 / 25.4; break;
480 case Unit::CM: retval.value.dValue = 1.0 / 2.54; break;
481 default:
482 case Unit::Invalid: break;
483 }
484 }
485 else if( m_defaultUnits == Unit::Mil )
486 {
487 switch( convertFrom )
488 {
489 case Unit::Inch: retval.value.dValue = 1.0 * 1000.0; break;
490 case Unit::Mil: retval.value.dValue = 1.0; break;
491 case Unit::UM: retval.value.dValue = 1.0 / 25.4; break;
492 case Unit::MM: retval.value.dValue = 1000.0 / 25.4; break;
493 case Unit::CM: retval.value.dValue = 1000.0 / 2.54; break;
494 default:
495 case Unit::Invalid: break;
496 }
497 }
498 else if( m_defaultUnits == Unit::Degrees && convertFrom == Unit::Degrees )
499 {
500 retval.value.dValue = 1.0;
501 }
502 else if( m_defaultUnits == Unit::SI )
503 {
504 retval.value.dValue = siScaler;
505 }
507 {
508 switch( convertFrom )
509 {
510 case Unit::Picoseconds: retval.value.dValue = 1.0; break;
511 case Unit::Femtoseconds: retval.value.dValue = 1.0 / 1000.0; break;
512 default:
513 case Unit::Invalid: break;
514 }
515 }
517 {
518 switch( convertFrom )
519 {
520 case Unit::Picoseconds: retval.value.dValue = 1000.0; break;
521 case Unit::Femtoseconds: retval.value.dValue = 1.0; break;
522 default:
523 case Unit::Invalid: break;
524 }
525 }
526 else if( m_defaultUnits == Unit::PsPerInch )
527 {
528 switch( convertFrom )
529 {
530 case Unit::PsPerInch: retval.value.dValue = 1.0; break;
531 case Unit::PsPerCm: retval.value.dValue = 2.54; break;
532 case Unit::PsPerMm: retval.value.dValue = 25.4; break;
533 default:
534 case Unit::Invalid: break;
535 }
536 }
537 else if( m_defaultUnits == Unit::PsPerCm )
538 {
539 switch( convertFrom )
540 {
541 case Unit::PsPerInch: retval.value.dValue = 1.0 / 2.54; break;
542 case Unit::PsPerCm: retval.value.dValue = 1.0; break;
543 case Unit::PsPerMm: retval.value.dValue = 10.0; break;
544 default:
545 case Unit::Invalid: break;
546 }
547 }
548 else if( m_defaultUnits == Unit::PsPerMm )
549 {
550 switch( convertFrom )
551 {
552 case Unit::PsPerInch: retval.value.dValue = 1.0 / 25.4; break;
553 case Unit::PsPerCm: retval.value.dValue = 1.0 / 10.0; break;
554 case Unit::PsPerMm: retval.value.dValue = 1.0; break;
555 default:
556 case Unit::Invalid: break;
557 }
558 }
559 }
560 else if( isalpha( ch ) )
561 {
562 // VAR
563 const char* cptr = &m_token.input[ m_token.pos ];
564 cptr++;
565
566 while( isalnum( *cptr ) )
567 cptr++;
568
569 retval.token = VAR;
570 size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
571
572 if( bytesToCopy >= sizeof( retval.value.text ) )
573 bytesToCopy = sizeof( retval.value.text ) - 1;
574
575 strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
576 retval.value.text[ bytesToCopy ] = 0;
577 m_token.pos += cptr - &m_token.input[ m_token.pos ];
578 }
579 else
580 {
581 // Single char tokens
582 switch( ch )
583 {
584 case '+': retval.token = PLUS; break;
585 case '-': retval.token = MINUS; break;
586 case '*': retval.token = MULT; break;
587 case '/': retval.token = DIVIDE; break;
588 case '(': retval.token = PARENL; break;
589 case ')': retval.token = PARENR; break;
590 case '=': retval.token = ASSIGN; break;
591 case ';': retval.token = SEMCOL; break;
592 default: m_parseError = true; break; /* invalid character */
593 }
594
595 m_token.pos++;
596 }
597
598 if( !m_parseError )
599 retval.value.valid = true;
600
601 return retval;
602}
603
604
605void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
606{
607 m_varMap[ aString ] = aValue;
608}
609
610
611double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
612{
613 auto it = m_varMap.find( aString );
614
615 if( it != m_varMap.end() )
616 {
617 return it->second;
618 }
619 else
620 {
621 m_parseError = true;
622 return 0.0;
623 }
624}
static bool isOldSchoolDecimalSeparator(char ch, double *siScaler)
static bool IsOldSchoolDecimalSeparator(wxUniChar ch, double *siScaler)
wxString OriginalText() const
static bool IsDecimalSeparator(char ch, char localeSeparator, bool allowInfixNotation)
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
static bool IsDigit(char ch)
EDA_UNITS
Definition eda_units.h:48
@ PS_PER_INCH
Definition eda_units.h:59
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 ...
@ VALUE
Field Value of part, i.e. "3.3K".