KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_text_eval_parser_core.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
28
30
31// Code under test
33
34#include <memory>
35#include <unordered_map>
36
37using namespace calc_parser;
38
42BOOST_AUTO_TEST_SUITE( TextEvalParserLowLevel )
43
44
48{
49 auto variables = std::make_shared<std::unordered_map<std::string, Value>>();
50
51 // Set up some test variables
52 (*variables)["x"] = 10.0;
53 (*variables)["y"] = 5.0;
54 (*variables)["name"] = std::string("KiCad");
55 (*variables)["pi"] = 3.14159;
56
57 return [variables]( const std::string& varName ) -> Result<Value>
58 {
59 auto it = variables->find( varName );
60 if( it != variables->end() )
61 return MakeValue( it->second );
62
63 return MakeError<Value>( "Variable not found: " + varName );
64 };
65}
66
71{
72 // Test ToDouble conversion
73 {
74 Value numVal = 42.5;
76 BOOST_CHECK( result.HasValue() );
77 BOOST_CHECK_CLOSE( result.GetValue(), 42.5, 0.001 );
78 }
79
80 {
81 Value strVal = std::string("123.45");
83 BOOST_CHECK( result.HasValue() );
84 BOOST_CHECK_CLOSE( result.GetValue(), 123.45, 0.001 );
85 }
86
87 {
88 Value invalidStr = std::string("not_a_number");
89 auto result = calc_parser::VALUE_UTILS::ToDouble( invalidStr );
90 BOOST_CHECK( result.HasError() );
91 }
92
93 // Test ToString conversion
94 {
95 Value numVal = 42.0;
98 }
99
100 {
101 Value strVal = std::string("Hello");
103 BOOST_CHECK_EQUAL( result, "Hello" );
104 }
105
106 // Test IsTruthy
107 {
108 BOOST_CHECK( calc_parser::VALUE_UTILS::IsTruthy( Value{1.0} ) );
109 BOOST_CHECK( !calc_parser::VALUE_UTILS::IsTruthy( Value{0.0} ) );
110 BOOST_CHECK( calc_parser::VALUE_UTILS::IsTruthy( Value{std::string("non-empty")} ) );
111 BOOST_CHECK( !calc_parser::VALUE_UTILS::IsTruthy( Value{std::string("")} ) );
112 }
113
114 // Test ArithmeticOp
115 {
116 Value left = 10.0;
117 Value right = 3.0;
118
119 auto addResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '+' );
120 BOOST_CHECK( addResult.HasValue() );
121 BOOST_CHECK_CLOSE( std::get<double>( addResult.GetValue() ), 13.0, 0.001 );
122
123 auto subResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '-' );
124 BOOST_CHECK( subResult.HasValue() );
125 BOOST_CHECK_CLOSE( std::get<double>( subResult.GetValue() ), 7.0, 0.001 );
126
127 auto mulResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '*' );
128 BOOST_CHECK( mulResult.HasValue() );
129 BOOST_CHECK_CLOSE( std::get<double>( mulResult.GetValue() ), 30.0, 0.001 );
130
131 auto divResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '/' );
132 BOOST_CHECK( divResult.HasValue() );
133 BOOST_CHECK_CLOSE( std::get<double>( divResult.GetValue() ), 3.333, 0.1 );
134
135 auto modResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '%' );
136 BOOST_CHECK( modResult.HasValue() );
137 BOOST_CHECK_CLOSE( std::get<double>( modResult.GetValue() ), 1.0, 0.001 );
138
139 auto powResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '^' );
140 BOOST_CHECK( powResult.HasValue() );
141 BOOST_CHECK_CLOSE( std::get<double>( powResult.GetValue() ), 1000.0, 0.001 );
142 }
143
144 // Test division by zero
145 {
146 Value left = 10.0;
147 Value right = 0.0;
148
149 auto divResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '/' );
150 BOOST_CHECK( divResult.HasError() );
151
152 auto modResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '%' );
153 BOOST_CHECK( modResult.HasError() );
154 }
155
156 // Test ConcatStrings
157 {
158 Value left = std::string("Hello ");
159 Value right = std::string("World");
161 BOOST_CHECK_EQUAL( std::get<std::string>( result ), "Hello World" );
162 }
163
164 {
165 Value left = 42.0;
166 Value right = std::string(" items");
168 BOOST_CHECK_EQUAL( std::get<std::string>( result ), "42 items" );
169 }
170}
171
175BOOST_AUTO_TEST_CASE( NodeCreation )
176{
177 // Test number node
178 {
179 auto node = NODE::CreateNumber( 42.5 );
180 BOOST_CHECK( node->type == NodeType::Number );
181 BOOST_CHECK_CLOSE( std::get<double>( node->data ), 42.5, 0.001 );
182 }
183
184 // Test string node
185 {
186 auto node = NODE::CreateString( "Hello World" );
187 BOOST_CHECK( node->type == NodeType::String );
188 BOOST_CHECK_EQUAL( std::get<std::string>( node->data ), "Hello World" );
189 }
190
191 // Test variable node
192 {
193 auto node = NODE::CreateVar( "testVar" );
194 BOOST_CHECK( node->type == NodeType::Var );
195 BOOST_CHECK_EQUAL( std::get<std::string>( node->data ), "testVar" );
196 }
197
198 // Test binary operation node
199 {
200 auto left = NODE::CreateNumber( 10.0 );
201 auto right = NODE::CreateNumber( 5.0 );
202 auto binOp = NODE::CreateBinOp( std::move( left ), '+', std::move( right ) );
203
204 BOOST_CHECK( binOp->type == NodeType::BinOp );
205 const auto& binOpData = std::get<BIN_OP_DATA>( binOp->data );
206 BOOST_CHECK( binOpData.op == '+' );
207 BOOST_CHECK( binOpData.left != nullptr );
208 BOOST_CHECK( binOpData.right != nullptr );
209 }
210
211 // Test function node
212 {
213 std::vector<std::unique_ptr<NODE>> args;
214 args.push_back( NODE::CreateNumber( 5.0 ) );
215
216 auto funcNode = NODE::CreateFunction( "abs", std::move( args ) );
217 BOOST_CHECK( funcNode->type == NodeType::Function );
218
219 const auto& funcData = std::get<FUNC_DATA>( funcNode->data );
220 BOOST_CHECK_EQUAL( funcData.name, "abs" );
221 BOOST_CHECK_EQUAL( funcData.args.size(), 1 );
222 }
223}
224
228BOOST_AUTO_TEST_CASE( EvaluationVisitor )
229{
230 ERROR_COLLECTOR errors;
231 auto varResolver = CreateTestVariableResolver();
232 calc_parser::EVAL_VISITOR evaluator( varResolver, errors );
233
234 // Test number evaluation
235 {
236 auto node = NODE::CreateNumber( 42.5 );
237 auto result = node->Accept( evaluator );
238
239 BOOST_CHECK( result.HasValue() );
240 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
241 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 42.5, 0.001 );
242 }
243
244 // Test string evaluation
245 {
246 auto node = NODE::CreateString( "Hello" );
247 auto result = node->Accept( evaluator );
248
249 BOOST_CHECK( result.HasValue() );
250 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
251 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "Hello" );
252 }
253
254 // Test variable evaluation
255 {
256 auto node = NODE::CreateVar( "x" );
257 auto result = node->Accept( evaluator );
258
259 BOOST_CHECK( result.HasValue() );
260 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
261 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 10.0, 0.001 );
262 }
263
264 // Test undefined variable
265 {
266 auto node = NODE::CreateVar( "undefined" );
267 auto result = node->Accept( evaluator );
268
269 BOOST_CHECK( result.HasError() );
270 }
271
272 // Test binary operation
273 {
274 auto left = NODE::CreateNumber( 10.0 );
275 auto right = NODE::CreateNumber( 5.0 );
276 auto binOp = NODE::CreateBinOp( std::move( left ), '+', std::move( right ) );
277
278 auto result = binOp->Accept( evaluator );
279
280 BOOST_CHECK( result.HasValue() );
281 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 15.0, 0.001 );
282 }
283
284 // Test string concatenation with +
285 {
286 auto left = NODE::CreateString( "Hello " );
287 auto right = NODE::CreateString( "World" );
288 auto binOp = NODE::CreateBinOp( std::move( left ), '+', std::move( right ) );
289
290 auto result = binOp->Accept( evaluator );
291
292 BOOST_CHECK( result.HasValue() );
293 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
294 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "Hello World" );
295 }
296}
297
301BOOST_AUTO_TEST_CASE( FunctionEvaluation )
302{
303 ERROR_COLLECTOR errors;
304 auto varResolver = CreateTestVariableResolver();
305 calc_parser::EVAL_VISITOR evaluator( varResolver, errors );
306
307 // Test abs function
308 {
309 std::vector<std::unique_ptr<NODE>> args;
310 args.push_back( NODE::CreateNumber( -5.0 ) );
311
312 auto funcNode = NODE::CreateFunction( "abs", std::move( args ) );
313 auto result = funcNode->Accept( evaluator );
314
315 BOOST_CHECK( result.HasValue() );
316 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 5.0, 0.001 );
317 }
318
319 // Test sqrt function
320 {
321 std::vector<std::unique_ptr<NODE>> args;
322 args.push_back( NODE::CreateNumber( 16.0 ) );
323
324 auto funcNode = NODE::CreateFunction( "sqrt", std::move( args ) );
325 auto result = funcNode->Accept( evaluator );
326
327 BOOST_CHECK( result.HasValue() );
328 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 4.0, 0.001 );
329 }
330
331 // Test sqrt with negative number (should error)
332 {
333 std::vector<std::unique_ptr<NODE>> args;
334 args.push_back( NODE::CreateNumber( -1.0 ) );
335
336 auto funcNode = NODE::CreateFunction( "sqrt", std::move( args ) );
337 auto result = funcNode->Accept( evaluator );
338
339 BOOST_CHECK( result.HasError() );
340 }
341
342 // Test max function
343 {
344 std::vector<std::unique_ptr<NODE>> args;
345 args.push_back( NODE::CreateNumber( 3.0 ) );
346 args.push_back( NODE::CreateNumber( 7.0 ) );
347 args.push_back( NODE::CreateNumber( 1.0 ) );
348
349 auto funcNode = NODE::CreateFunction( "max", std::move( args ) );
350 auto result = funcNode->Accept( evaluator );
351
352 BOOST_CHECK( result.HasValue() );
353 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 7.0, 0.001 );
354 }
355
356 // Test string function - upper
357 {
358 std::vector<std::unique_ptr<NODE>> args;
359 args.push_back( NODE::CreateString( "hello" ) );
360
361 auto funcNode = NODE::CreateFunction( "upper", std::move( args ) );
362 auto result = funcNode->Accept( evaluator );
363
364 BOOST_CHECK( result.HasValue() );
365 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
366 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "HELLO" );
367 }
368
369 // Test format function
370 {
371 std::vector<std::unique_ptr<NODE>> args;
372 args.push_back( NODE::CreateNumber( 3.14159 ) );
373 args.push_back( NODE::CreateNumber( 2.0 ) );
374
375 auto funcNode = NODE::CreateFunction( "format", std::move( args ) );
376 auto result = funcNode->Accept( evaluator );
377
378 BOOST_CHECK( result.HasValue() );
379 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
380 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "3.14" );
381 }
382
383 // Test unknown function
384 {
385 std::vector<std::unique_ptr<NODE>> args;
386 args.push_back( NODE::CreateNumber( 1.0 ) );
387
388 auto funcNode = NODE::CreateFunction( "unknownfunc", std::move( args ) );
389 auto result = funcNode->Accept( evaluator );
390
391 BOOST_CHECK( result.HasError() );
392 }
393
394 // Test zero-argument functions
395 {
396 std::vector<std::unique_ptr<NODE>> args; // Empty args
397
398 auto todayNode = NODE::CreateFunction( "today", std::move( args ) );
399 auto result = todayNode->Accept( evaluator );
400
401 BOOST_CHECK( result.HasValue() );
402 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
403 // Should return a reasonable number of days since epoch
404 auto days = std::get<double>( result.GetValue() );
405 BOOST_CHECK_GT( days, 18000 ); // Should be after year 2019
406 }
407
408 {
409 std::vector<std::unique_ptr<NODE>> args; // Empty args
410
411 auto randomNode = NODE::CreateFunction( "random", std::move( args ) );
412 auto result = randomNode->Accept( evaluator );
413
414 BOOST_CHECK( result.HasValue() );
415 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
416 auto randomVal = std::get<double>( result.GetValue() );
417 BOOST_CHECK_GE( randomVal, 0.0 );
418 BOOST_CHECK_LT( randomVal, 1.0 );
419 }
420}
421
425BOOST_AUTO_TEST_CASE( DocumentProcessor )
426{
427 auto varResolver = CreateTestVariableResolver();
428
429 // Create a simple document with text and calculations
430 auto doc = std::make_unique<DOC>();
431
432 // Add text node
433 doc->AddNode( NODE::CreateText( "Value is " ) );
434
435 // Add calculation node
436 auto calcExpr = NODE::CreateBinOp(
437 NODE::CreateNumber( 2.0 ),
438 '+',
439 NODE::CreateNumber( 3.0 )
440 );
441 doc->AddNode( NODE::CreateCalc( std::move( calcExpr ) ) );
442
443 // Add more text
444 doc->AddNode( NODE::CreateText( " units" ) );
445
446 // Process the document
447 auto [result, hadErrors] = calc_parser::DOC_PROCESSOR::Process( *doc, varResolver );
448
449 BOOST_CHECK( !hadErrors );
450 BOOST_CHECK_EQUAL( result, "Value is 5 units" );
451}
452
456BOOST_AUTO_TEST_CASE( ErrorHandling )
457{
458 ERROR_COLLECTOR errors;
459
460 // Test adding errors
461 errors.AddError( "Test error 1" );
462 errors.AddWarning( "Test warning 1" );
463 errors.AddError( "Test error 2" );
464
465 BOOST_CHECK( errors.HasErrors() );
466 BOOST_CHECK( errors.HasWarnings() );
467
468 const auto& errorList = errors.GetErrors();
469 BOOST_CHECK_EQUAL( errorList.size(), 2 );
470 BOOST_CHECK_EQUAL( errorList[0], "Test error 1" );
471 BOOST_CHECK_EQUAL( errorList[1], "Test error 2" );
472
473 const auto& warningList = errors.GetWarnings();
474 BOOST_CHECK_EQUAL( warningList.size(), 1 );
475 BOOST_CHECK_EQUAL( warningList[0], "Test warning 1" );
476
477 // Test error message formatting
478 auto allMessages = errors.GetAllMessages();
479 BOOST_CHECK( allMessages.find( "Error: Test error 1" ) != std::string::npos );
480 BOOST_CHECK( allMessages.find( "Warning: Test warning 1" ) != std::string::npos );
481
482 // Test clearing
483 errors.Clear();
484 BOOST_CHECK( !errors.HasErrors() );
485 BOOST_CHECK( !errors.HasWarnings() );
486}
487
492{
493 // Test string token
494 {
495 auto token = MakeStringToken( "Hello World" );
496 BOOST_CHECK( token.isString );
497 BOOST_CHECK_EQUAL( GetTokenString( token ), "Hello World" );
498 }
499
500 // Test number token
501 {
502 auto token = MakeNumberToken( 42.5 );
503 BOOST_CHECK( !token.isString );
504 BOOST_CHECK_CLOSE( GetTokenDouble( token ), 42.5, 0.001 );
505 }
506}
507
static auto Process(const DOC &aDoc, VariableCallback aVariableCallback) -> std::pair< std::string, bool >
Process document using callback for variable resolution.
auto GetErrors() const -> const std::vector< std::string > &
auto AddError(std::string aError) -> void
auto GetWarnings() const -> const std::vector< std::string > &
auto GetAllMessages() const -> std::string
auto HasWarnings() const -> bool
auto AddWarning(std::string aWarning) -> void
auto HasErrors() const -> bool
static auto CreateVar(std::string aName) -> std::unique_ptr< NODE >
static auto CreateNumber(double aValue) -> std::unique_ptr< NODE >
static auto CreateFunction(std::string aName, std::vector< std::unique_ptr< NODE > > aArgs) -> std::unique_ptr< NODE >
static auto CreateText(std::string aText) -> std::unique_ptr< NODE >
static auto CreateBinOp(std::unique_ptr< NODE > aLeft, char aOp, std::unique_ptr< NODE > aRight) -> std::unique_ptr< NODE >
static auto CreateString(std::string aValue) -> std::unique_ptr< NODE >
static auto CreateCalc(std::unique_ptr< NODE > aExpr) -> std::unique_ptr< NODE >
static auto ArithmeticOp(const Value &aLeft, const Value &aRight, char aOp) -> Result< Value >
static auto ToString(const Value &aVal) -> std::string
static auto IsTruthy(const Value &aVal) -> bool
static auto ToDouble(const Value &aVal) -> Result< double >
static auto ConcatStrings(const Value &aLeft, const Value &aRight) -> Value
TOKEN_TYPE MakeNumberToken(double val)
auto MakeValue(T aVal) -> Result< T >
std::variant< double, std::string > Value
TOKEN_TYPE MakeStringToken(const std::string &str)
std::string GetTokenString(const TOKEN_TYPE &token)
double GetTokenDouble(const TOKEN_TYPE &token)
auto MakeError(std::string aMsg) -> Result< T >
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(ValueUtils)
Test VALUE_UTILS functionality.
auto CreateTestVariableResolver()
Declare the test suite.