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::MM: m_defaultUnits = Unit::MM; break;
86 case EDA_UNITS::MILS: m_defaultUnits = Unit::Mil; break;
87 case EDA_UNITS::INCH: m_defaultUnits = Unit::Inch; break;
88 case EDA_UNITS::DEGREES: m_defaultUnits = Unit::Degrees; break;
89 case EDA_UNITS::FS: m_defaultUnits = Unit::Femtoseconds; break;
90 case EDA_UNITS::PS: m_defaultUnits = Unit::Picoseconds; break;
91 case EDA_UNITS::PS_PER_INCH: m_defaultUnits = Unit::PsPerInch; break;
92 case EDA_UNITS::PS_PER_CM: m_defaultUnits = Unit::PsPerCm; break;
93 case EDA_UNITS::PS_PER_MM: m_defaultUnits = Unit::PsPerMm; break;
94 case EDA_UNITS::UNSCALED: m_defaultUnits = Unit::SI; break;
95 default: m_defaultUnits = Unit::MM; break;
96 }
97}
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
196{
197 Token retval;
198 size_t idx;
199
200 retval.token = ENDS;
201 retval.value.dValue = 0;
202 retval.value.valid = false;
203 retval.value.text[0] = 0;
204
205 if( m_token.token == nullptr )
206 return retval;
207
208 if( m_token.input == nullptr )
209 return retval;
210
211 if( m_token.pos >= m_token.inputLen )
212 return retval;
213
214 // Support for old school decimal separators (ie: "2K2")
215 auto isOldSchoolDecimalSeparator =
216 []( char ch, double* siScaler ) -> bool
217 {
218 switch( ch )
219 {
220 case 'a': *siScaler = 1.0e-18; return true;
221 case 'f': *siScaler = 1.0e-15; return true;
222 case 'p': *siScaler = 1.0e-12; return true;
223 case 'n': *siScaler = 1.0e-9; return true;
224 case 'u': *siScaler = 1.0e-6; return true;
225 case 'm': *siScaler = 1.0e-3; return true;
226 case 'k':
227 case 'K': *siScaler = 1.0e3; return true;
228 case 'M': *siScaler = 1.0e6; return true;
229 case 'G': *siScaler = 1.0e9; return true;
230 case 'T': *siScaler = 1.0e12; return true;
231 case 'P': *siScaler = 1.0e15; return true;
232 case 'E': *siScaler = 1.0e18; return true;
233 default: return false;
234 }
235 };
236
237 auto isDecimalSeparator =
238 [&]( char ch ) -> bool
239 {
240 double dummy;
241
242 if( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' )
243 return true;
244
245 if( m_defaultUnits == Unit::SI && isOldSchoolDecimalSeparator( ch, &dummy ) )
246 return true;
247
248 return false;
249 };
250
251 // Lambda: get value as string, store into clToken.token and update current index.
252 auto extractNumber =
253 [&]( double* aScaler )
254 {
255 bool haveSeparator = false;
256 double siScaler = 1.0;
257 char ch = m_token.input[ m_token.pos ];
258
259 idx = 0;
260
261 do
262 {
263 if( isDecimalSeparator( ch ) )
264 {
265 if( haveSeparator )
266 break;
267 else
268 haveSeparator = true;
269
270 if( isOldSchoolDecimalSeparator( ch, &siScaler ) )
271 *aScaler = siScaler;
272
274 }
275 else
276 {
277 m_token.token[ idx++ ] = ch;
278 }
279
280 ch = m_token.input[++m_token.pos];
281
282 // the below static cast is to avoid partial unicode chars triggering an
283 // assert in isdigit on msvc
284 } while( isdigit( static_cast<unsigned char>( ch ) ) || isDecimalSeparator( ch ) );
285
286 m_token.token[ idx ] = 0;
287 };
288
289 // Lamda: Get unit for current token.
290 // Valid units are ", in, um, cm, mm, mil, ps and thou. Returns Unit::Invalid otherwise.
291 auto checkUnit =
292 [&]( double* siScaler ) -> Unit
293 {
294 char ch = m_token.input[ m_token.pos ];
295
296 if( ch == '"' )
297 {
298 m_token.pos++;
299 return Unit::Inch;
300 }
301
302 // Do not use strcasecmp() as it is not available on all platforms
303 const char* cptr = &m_token.input[ m_token.pos ];
304 const auto sizeLeft = m_token.inputLen - m_token.pos;
305
306 // We should really give this unicode support
307 if( sizeLeft >= 2 && ch == '\xC2' && cptr[1] == '\xB0' )
308 {
309 m_token.pos += 2;
310 return Unit::Degrees;
311 }
312
313 // Ideally we should also handle the unicode characters that can be used for micro,
314 // but unicode handling in this tokenizer doesn't work.
315 // (e.g. add support for μm (µ is MICRO SIGN), µm (µ is GREEK SMALL LETTER MU)
316 // later)
317 if( sizeLeft >= 2 && ch == 'u' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
318 {
319 m_token.pos += 2;
320 return Unit::UM;
321 }
322
323 if( sizeLeft >= 5 && ch == 'p' && cptr[1] == 's' && cptr[2] == '/' && cptr[3] == 'i'
324 && cptr[4] == 'n' && !isalnum( cptr[5] ) )
325 {
326 m_token.pos += 5;
327 return Unit::PsPerInch;
328 }
329
330 if( sizeLeft >= 5 && ch == 'p' && cptr[1] == 's' && cptr[2] == '/' && cptr[3] == 'c'
331 && cptr[4] == 'm' && !isalnum( cptr[5] ) )
332 {
333 m_token.pos += 5;
334 return Unit::PsPerCm;
335 }
336
337 if( sizeLeft >= 5 && ch == 'p' && cptr[1] == 's' && cptr[2] == '/' && cptr[3] == 'm'
338 && cptr[4] == 'm' && !isalnum( cptr[5] ) )
339 {
340 m_token.pos += 5;
341 return Unit::PsPerMm;
342 }
343
344 if( sizeLeft >= 2 && ch == 'f' && cptr[1] == 's' && !isalnum( cptr[2] ) )
345 {
346 m_token.pos += 2;
347 return Unit::Femtoseconds;
348 }
349
350 if( sizeLeft >= 2 && ch == 'p' && cptr[1] == 's' && !isalnum( cptr[2] ) )
351 {
352 m_token.pos += 2;
353 return Unit::Picoseconds;
354 }
355
356 if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
357 {
358 m_token.pos += 2;
359 return Unit::MM;
360 }
361
362 if( sizeLeft >= 2 && ch == 'c' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
363 {
364 m_token.pos += 2;
365 return Unit::CM;
366 }
367
368 if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ) )
369 {
370 m_token.pos += 2;
371 return Unit::Inch;
372 }
373
374 if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l'
375 && !isalnum( cptr[ 3 ] ) )
376 {
377 m_token.pos += 3;
378 return Unit::Mil;
379 }
380
381 if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o'
382 && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ) )
383 {
384 m_token.pos += 4;
385 return Unit::Mil;
386 }
387
388 if( m_defaultUnits == Unit::SI && sizeLeft >= 1
389 && isOldSchoolDecimalSeparator( ch, siScaler ) )
390 {
391 m_token.pos++;
392 return Unit::SI;
393 }
394
395 return Unit::Invalid;
396 };
397
398 char ch;
399
400 // Start processing of first/next token: Remove whitespace
401 for( ;; )
402 {
403 ch = m_token.input[ m_token.pos ];
404
405 if( ch == ' ' )
406 m_token.pos++;
407 else
408 break;
409 }
410
411 double siScaler = 1.0;
412 Unit convertFrom = Unit::Invalid;
413
414 if( ch == 0 )
415 {
416 /* End of input */
417 }
418 else if( isdigit( static_cast<unsigned char>( ch ) ) || isDecimalSeparator( ch ) )
419 {
420 // the above static cast is to avoid partial unicode chars triggering an assert in
421 // isdigit on msvc
422 // VALUE
423 extractNumber( &siScaler );
424 retval.token = VALUE;
425 retval.value.dValue = atof( m_token.token ) * siScaler;
426 }
427 else if( ( convertFrom = checkUnit( &siScaler ) ) != Unit::Invalid )
428 {
429 // UNIT
430 // Units are appended to a VALUE.
431 // Determine factor to default unit if unit for value is given.
432 // Example: Default is mm, unit is inch: factor is 25.4
433 // The factor is assigned to the terminal UNIT. The actual
434 // conversion is done within a parser action.
435 retval.token = UNIT;
436
437 if( m_defaultUnits == Unit::MM )
438 {
439 switch( convertFrom )
440 {
441 case Unit::Inch: retval.value.dValue = 25.4; break;
442 case Unit::Mil: retval.value.dValue = 25.4 / 1000.0; break;
443 case Unit::UM: retval.value.dValue = 1 / 1000.0; break;
444 case Unit::MM: retval.value.dValue = 1.0; break;
445 case Unit::CM: retval.value.dValue = 10.0; break;
446 default:
447 case Unit::Invalid: break;
448 }
449 }
450 else if( m_defaultUnits == Unit::Inch )
451 {
452 switch( convertFrom )
453 {
454 case Unit::Inch: retval.value.dValue = 1.0; break;
455 case Unit::Mil: retval.value.dValue = 1.0 / 1000.0; break;
456 case Unit::UM: retval.value.dValue = 1.0 / 25400.0; break;
457 case Unit::MM: retval.value.dValue = 1.0 / 25.4; break;
458 case Unit::CM: retval.value.dValue = 1.0 / 2.54; break;
459 default:
460 case Unit::Invalid: break;
461 }
462 }
463 else if( m_defaultUnits == Unit::Mil )
464 {
465 switch( convertFrom )
466 {
467 case Unit::Inch: retval.value.dValue = 1.0 * 1000.0; break;
468 case Unit::Mil: retval.value.dValue = 1.0; break;
469 case Unit::UM: retval.value.dValue = 1.0 / 25.4; break;
470 case Unit::MM: retval.value.dValue = 1000.0 / 25.4; break;
471 case Unit::CM: retval.value.dValue = 1000.0 / 2.54; break;
472 default:
473 case Unit::Invalid: break;
474 }
475 }
476 else if( m_defaultUnits == Unit::Degrees && convertFrom == Unit::Degrees )
477 {
478 retval.value.dValue = 1.0;
479 }
480 else if( m_defaultUnits == Unit::SI )
481 {
482 retval.value.dValue = siScaler;
483 }
485 {
486 switch( convertFrom )
487 {
488 case Unit::Picoseconds: retval.value.dValue = 1.0; break;
489 case Unit::Femtoseconds: retval.value.dValue = 1.0 / 1000.0; break;
490 default:
491 case Unit::Invalid: break;
492 }
493 }
495 {
496 switch( convertFrom )
497 {
498 case Unit::Picoseconds: retval.value.dValue = 1000.0; break;
499 case Unit::Femtoseconds: retval.value.dValue = 1.0; break;
500 default:
501 case Unit::Invalid: break;
502 }
503 }
504 else if( m_defaultUnits == Unit::PsPerInch )
505 {
506 switch( convertFrom )
507 {
508 case Unit::PsPerInch: retval.value.dValue = 1.0; break;
509 case Unit::PsPerCm: retval.value.dValue = 2.54; break;
510 case Unit::PsPerMm: retval.value.dValue = 25.4; break;
511 default:
512 case Unit::Invalid: break;
513 }
514 }
515 else if( m_defaultUnits == Unit::PsPerCm )
516 {
517 switch( convertFrom )
518 {
519 case Unit::PsPerInch: retval.value.dValue = 1.0 / 2.54; break;
520 case Unit::PsPerCm: retval.value.dValue = 1.0; break;
521 case Unit::PsPerMm: retval.value.dValue = 10.0; break;
522 default:
523 case Unit::Invalid: break;
524 }
525 }
526 else if( m_defaultUnits == Unit::PsPerMm )
527 {
528 switch( convertFrom )
529 {
530 case Unit::PsPerInch: retval.value.dValue = 1.0 / 25.4; break;
531 case Unit::PsPerCm: retval.value.dValue = 1.0 / 10.0; break;
532 case Unit::PsPerMm: retval.value.dValue = 1.0; break;
533 default:
534 case Unit::Invalid: break;
535 }
536 }
537 }
538 else if( isalpha( ch ) )
539 {
540 // VAR
541 const char* cptr = &m_token.input[ m_token.pos ];
542 cptr++;
543
544 while( isalnum( *cptr ) )
545 cptr++;
546
547 retval.token = VAR;
548 size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
549
550 if( bytesToCopy >= sizeof( retval.value.text ) )
551 bytesToCopy = sizeof( retval.value.text ) - 1;
552
553 strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
554 retval.value.text[ bytesToCopy ] = 0;
555 m_token.pos += cptr - &m_token.input[ m_token.pos ];
556 }
557 else
558 {
559 // Single char tokens
560 switch( ch )
561 {
562 case '+': retval.token = PLUS; break;
563 case '-': retval.token = MINUS; break;
564 case '*': retval.token = MULT; break;
565 case '/': retval.token = DIVIDE; break;
566 case '(': retval.token = PARENL; break;
567 case ')': retval.token = PARENR; break;
568 case '=': retval.token = ASSIGN; break;
569 case ';': retval.token = SEMCOL; break;
570 default: m_parseError = true; break; /* invalid character */
571 }
572
573 m_token.pos++;
574 }
575
576 if( !m_parseError )
577 retval.value.valid = true;
578
579 return retval;
580}
581
582
583void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
584{
585 m_varMap[ aString ] = aValue;
586}
587
588
589double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
590{
591 auto it = m_varMap.find( aString );
592
593 if( it != m_varMap.end() )
594 {
595 return it->second;
596 }
597 else
598 {
599 m_parseError = true;
600 return 0.0;
601 }
602}
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:48
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
@ VALUE
Field Value of part, i.e. "3.3K".