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, [email protected]
5 * Copyright (C) 2021-2022 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{
49 struct lconv* lc = localeconv();
50 m_localeDecimalSeparator = *lc->decimal_point;
51
52 m_parseError = false;
53 m_parseFinished = false;
54
55 m_parser = numEval::ParseAlloc( malloc );
56
57 SetDefaultUnits( aUnits );
58}
59
60
62{
63 numEval::ParseFree( m_parser, free );
64
65 // Allow explicit call to destructor
66 m_parser = nullptr;
67
68 Clear();
69}
70
71
73{
74 delete[] m_token.token;
75 m_token.token = nullptr;
76 m_token.input = nullptr;
77 m_parseError = true;
78 m_originalText = wxEmptyString;
79}
80
81
83{
84 switch( aUnits )
85 {
90 default: m_defaultUnits = Unit::MM; break;
91 }
92}
93
94
95void NUMERIC_EVALUATOR::parseError( const char* s )
96{
97 m_parseError = true;
98}
99
100
102{
103 m_parseFinished = true;
104}
105
106
108{
109 if( std::isnan( val ) )
110 {
111 // Naively printing this with %g produces "nan" on some platforms
112 // and "-nan(ind)" on others (e.g. MSVC). So force a "standard" string.
113 snprintf( m_token.token, m_token.outputLen, "%s", "NaN" );
114 }
115 else
116 {
117 // Can be printed as a floating point
118 // Warning: DO NOT use a format like %f or %g, because they can create issues.
119 // especially %g can generate an exponent, incompatible with UNIT_BINDER
120 // Use the optimized UIDouble2Str
121 snprintf( m_token.token, m_token.outputLen, "%s", UIDouble2Str( val ).c_str() );
122 }
123}
124
125
127{
128 return m_originalText;
129}
130
131
132bool NUMERIC_EVALUATOR::Process( const wxString& aString )
133{
134 // Feed parser token after token until end of input.
135
136 newString( aString );
137 m_parseError = false;
138 m_parseFinished = false;
139 Token tok;
140
141 if( aString.IsEmpty() )
142 {
143 m_parseFinished = true;
144 return true;
145 }
146
147 do
148 {
149 tok = getToken();
150 numEval::Parse( m_parser, tok.token, tok.value, this );
151
152 if( m_parseFinished || tok.token == ENDS )
153 {
154 // Reset parser by passing zero as token ID, value is ignored.
155 numEval::Parse( m_parser, 0, tok.value, this );
156 break;
157 }
158 } while( tok.token );
159
160 return !m_parseError;
161}
162
163
164void NUMERIC_EVALUATOR::newString( const wxString& aString )
165{
166 Clear();
167
168 m_originalText = aString;
169 m_token.input = aString.mb_str();
170 m_token.inputLen = strlen( m_token.input );
171 m_token.outputLen = std::max<std::size_t>( 64, m_token.inputLen + 1 );
172 m_token.pos = 0;
173 m_token.token = new char[m_token.outputLen]();
174 m_token.token[0] = '0';
175
176 m_parseFinished = false;
177}
178
179
181{
182 Token retval;
183 size_t idx;
184
185 retval.token = ENDS;
186 retval.value.dValue = 0;
187 retval.value.valid = false;
188 retval.value.text[0] = 0;
189
190 if( m_token.token == nullptr )
191 return retval;
192
193 if( m_token.input == nullptr )
194 return retval;
195
196 if( m_token.pos >= m_token.inputLen )
197 return retval;
198
199 auto isDecimalSeparator =
200 [&]( char ch ) -> bool
201 {
202 return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
203 };
204
205 // Lambda: get value as string, store into clToken.token and update current index.
206 auto extractNumber =
207 [&]()
208 {
209 bool haveSeparator = false;
210 idx = 0;
211 char ch = m_token.input[ m_token.pos ];
212
213 do
214 {
215 if( isDecimalSeparator( ch ) && haveSeparator )
216 break;
217
218 m_token.token[ idx++ ] = ch;
219
220 if( isDecimalSeparator( ch ) )
221 haveSeparator = true;
222
223 ch = m_token.input[ ++m_token.pos ];
224 } while( isdigit( ch ) || isDecimalSeparator( ch ) );
225
226 m_token.token[ idx ] = 0;
227
228 // Ensure that the systems decimal separator is used
229 for( int i = strlen( m_token.token ); i; i-- )
230 {
231 if( isDecimalSeparator( m_token.token[ i - 1 ] ) )
233 }
234 };
235
236 // Lamda: Get unit for current token.
237 // Valid units are ", in, mm, mil and thou. Returns Unit::Invalid otherwise.
238 auto checkUnit =
239 [this]() -> Unit
240 {
241 char ch = m_token.input[ m_token.pos ];
242
243 if( ch == '"' )
244 {
245 m_token.pos++;
246 return Unit::Inch;
247 }
248
249 // Do not use strcasecmp() as it is not available on all platforms
250 const char* cptr = &m_token.input[ m_token.pos ];
251 const auto sizeLeft = m_token.inputLen - m_token.pos;
252
253 // We should really give this unicode support
254 if( sizeLeft >= 2 && ch == '\xC2' && cptr[1] == '\xB0' )
255 {
256 m_token.pos += 2;
257 return Unit::Degrees;
258 }
259
260 if( sizeLeft >= 2 && ch == 'm' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
261 {
262 m_token.pos += 2;
263 return Unit::MM;
264 }
265
266 if( sizeLeft >= 2 && ch == 'c' && cptr[ 1 ] == 'm' && !isalnum( cptr[ 2 ] ) )
267 {
268 m_token.pos += 2;
269 return Unit::CM;
270 }
271
272 if( sizeLeft >= 2 && ch == 'i' && cptr[ 1 ] == 'n' && !isalnum( cptr[ 2 ] ) )
273 {
274 m_token.pos += 2;
275 return Unit::Inch;
276 }
277
278 if( sizeLeft >= 3 && ch == 'm' && cptr[ 1 ] == 'i' && cptr[ 2 ] == 'l'
279 && !isalnum( cptr[ 3 ] ) )
280 {
281 m_token.pos += 3;
282 return Unit::Mil;
283 }
284
285 if( sizeLeft >= 4 && ch == 't' && cptr[ 1 ] == 'h' && cptr[ 2 ] == 'o'
286 && cptr[ 3 ] == 'u' && !isalnum( cptr[ 4 ] ) )
287 {
288 m_token.pos += 4;
289 return Unit::Mil;
290 }
291
292 return Unit::Invalid;
293 };
294
295 char ch;
296
297 // Start processing of first/next token: Remove whitespace
298 for( ;; )
299 {
300 ch = m_token.input[ m_token.pos ];
301
302 if( ch == ' ' )
303 m_token.pos++;
304 else
305 break;
306 }
307
308 Unit convertFrom;
309
310 if( ch == 0 )
311 {
312 /* End of input */
313 }
314 else if( isdigit( ch ) || isDecimalSeparator( ch ))
315 {
316 // VALUE
317 extractNumber();
318 retval.token = VALUE;
319 retval.value.dValue = atof( m_token.token );
320 }
321 else if( ( convertFrom = checkUnit() ) != Unit::Invalid )
322 {
323 // UNIT
324 // Units are appended to a VALUE.
325 // Determine factor to default unit if unit for value is given.
326 // Example: Default is mm, unit is inch: factor is 25.4
327 // The factor is assigned to the terminal UNIT. The actual
328 // conversion is done within a parser action.
329 retval.token = UNIT;
330
331 if( m_defaultUnits == Unit::MM )
332 {
333 switch( convertFrom )
334 {
335 case Unit::Inch: retval.value.dValue = 25.4; break;
336 case Unit::Mil: retval.value.dValue = 25.4 / 1000.0; break;
337 case Unit::MM: retval.value.dValue = 1.0; break;
338 case Unit::CM: retval.value.dValue = 10.0; break;
339 default:
340 case Unit::Invalid: break;
341 }
342 }
343 else if( m_defaultUnits == Unit::Inch )
344 {
345 switch( convertFrom )
346 {
347 case Unit::Inch: retval.value.dValue = 1.0; break;
348 case Unit::Mil: retval.value.dValue = 1.0 / 1000.0; break;
349 case Unit::MM: retval.value.dValue = 1.0 / 25.4; break;
350 case Unit::CM: retval.value.dValue = 1.0 / 2.54; break;
351 default:
352 case Unit::Invalid: break;
353 }
354 }
355 else if( m_defaultUnits == Unit::Mil )
356 {
357 switch( convertFrom )
358 {
359 case Unit::Inch: retval.value.dValue = 1.0 * 1000.0; break;
360 case Unit::Mil: retval.value.dValue = 1.0; break;
361 case Unit::MM: retval.value.dValue = 1000.0 / 25.4; break;
362 case Unit::CM: retval.value.dValue = 1000.0 / 2.54; break;
363 default:
364 case Unit::Invalid: break;
365 }
366 }
367 else if( m_defaultUnits == Unit::Degrees && convertFrom == Unit::Degrees )
368 {
369 retval.value.dValue = 1.0;
370 }
371 }
372 else if( isalpha( ch ) )
373 {
374 // VAR
375 const char* cptr = &m_token.input[ m_token.pos ];
376 cptr++;
377
378 while( isalnum( *cptr ))
379 cptr++;
380
381 retval.token = VAR;
382 size_t bytesToCopy = cptr - &m_token.input[ m_token.pos ];
383
384 if( bytesToCopy >= sizeof( retval.value.text ) )
385 bytesToCopy = sizeof( retval.value.text ) - 1;
386
387 strncpy( retval.value.text, &m_token.input[ m_token.pos ], bytesToCopy );
388 retval.value.text[ bytesToCopy ] = 0;
389 m_token.pos += cptr - &m_token.input[ m_token.pos ];
390 }
391 else
392 {
393 // Single char tokens
394 switch( ch )
395 {
396 case '+': retval.token = PLUS; break;
397 case '-': retval.token = MINUS; break;
398 case '*': retval.token = MULT; break;
399 case '/': retval.token = DIVIDE; break;
400 case '(': retval.token = PARENL; break;
401 case ')': retval.token = PARENR; break;
402 case '=': retval.token = ASSIGN; break;
403 case ';': retval.token = SEMCOL; break;
404 default: m_parseError = true; break; /* invalid character */
405 }
406
407 m_token.pos++;
408 }
409
410 if( !m_parseError )
411 retval.value.valid = true;
412
413 return retval;
414}
415
416void NUMERIC_EVALUATOR::SetVar( const wxString& aString, double aValue )
417{
418 m_varMap[ aString ] = aValue;
419}
420
421double NUMERIC_EVALUATOR::GetVar( const wxString& aString )
422{
423 if( m_varMap[ aString ] )
424 return m_varMap[ aString ];
425 else
426 return 0.0;
427}
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:43
PARSE_RESULT Parse(const std::string &aString, NOTATION aNotation=NOTATION::SI, SIM_VALUE::TYPE aValueType=SIM_VALUE::TYPE_FLOAT)
Definition: sim_value.cpp:189
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