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, see <https://www.gnu.org/licenses/>.
18 */
19
24
26
27// Code under test
29
30#include <memory>
31#include <unordered_map>
32
33using namespace calc_parser;
34
38BOOST_AUTO_TEST_SUITE( TextEvalParserLowLevel )
39
40
44{
45 auto variables = std::make_shared<std::unordered_map<std::string, Value>>();
46
47 // Set up some test variables
48 (*variables)["x"] = 10.0;
49 (*variables)["y"] = 5.0;
50 (*variables)["name"] = std::string("KiCad");
51 (*variables)["pi"] = 3.14159;
52
53 return [variables]( const std::string& varName ) -> Result<Value>
54 {
55 auto it = variables->find( varName );
56 if( it != variables->end() )
57 return MakeValue( it->second );
58
59 return MakeError<Value>( "Variable not found: " + varName );
60 };
61}
62
67{
68 // Test ToDouble conversion
69 {
70 Value numVal = 42.5;
72 BOOST_CHECK( result.HasValue() );
73 BOOST_CHECK_CLOSE( result.GetValue(), 42.5, 0.001 );
74 }
75
76 {
77 Value strVal = std::string("123.45");
79 BOOST_CHECK( result.HasValue() );
80 BOOST_CHECK_CLOSE( result.GetValue(), 123.45, 0.001 );
81 }
82
83 {
84 Value invalidStr = std::string("not_a_number");
85 auto result = calc_parser::VALUE_UTILS::ToDouble( invalidStr );
86 BOOST_CHECK( result.HasError() );
87 }
88
89 // Test ToString conversion
90 {
91 Value numVal = 42.0;
94 }
95
96 {
97 Value strVal = std::string("Hello");
99 BOOST_CHECK_EQUAL( result, "Hello" );
100 }
101
102 // Test IsTruthy
103 {
104 BOOST_CHECK( calc_parser::VALUE_UTILS::IsTruthy( Value{1.0} ) );
105 BOOST_CHECK( !calc_parser::VALUE_UTILS::IsTruthy( Value{0.0} ) );
106 BOOST_CHECK( calc_parser::VALUE_UTILS::IsTruthy( Value{std::string("non-empty")} ) );
107 BOOST_CHECK( !calc_parser::VALUE_UTILS::IsTruthy( Value{std::string("")} ) );
108 }
109
110 // Test ArithmeticOp
111 {
112 Value left = 10.0;
113 Value right = 3.0;
114
115 auto addResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '+' );
116 BOOST_CHECK( addResult.HasValue() );
117 BOOST_CHECK_CLOSE( std::get<double>( addResult.GetValue() ), 13.0, 0.001 );
118
119 auto subResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '-' );
120 BOOST_CHECK( subResult.HasValue() );
121 BOOST_CHECK_CLOSE( std::get<double>( subResult.GetValue() ), 7.0, 0.001 );
122
123 auto mulResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '*' );
124 BOOST_CHECK( mulResult.HasValue() );
125 BOOST_CHECK_CLOSE( std::get<double>( mulResult.GetValue() ), 30.0, 0.001 );
126
127 auto divResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '/' );
128 BOOST_CHECK( divResult.HasValue() );
129 BOOST_CHECK_CLOSE( std::get<double>( divResult.GetValue() ), 3.333, 0.1 );
130
131 auto modResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '%' );
132 BOOST_CHECK( modResult.HasValue() );
133 BOOST_CHECK_CLOSE( std::get<double>( modResult.GetValue() ), 1.0, 0.001 );
134
135 auto powResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '^' );
136 BOOST_CHECK( powResult.HasValue() );
137 BOOST_CHECK_CLOSE( std::get<double>( powResult.GetValue() ), 1000.0, 0.001 );
138 }
139
140 // Test division by zero
141 {
142 Value left = 10.0;
143 Value right = 0.0;
144
145 auto divResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '/' );
146 BOOST_CHECK( divResult.HasError() );
147
148 auto modResult = calc_parser::VALUE_UTILS::ArithmeticOp( left, right, '%' );
149 BOOST_CHECK( modResult.HasError() );
150 }
151
152 // Test ConcatStrings
153 {
154 Value left = std::string("Hello ");
155 Value right = std::string("World");
157 BOOST_CHECK_EQUAL( std::get<std::string>( result ), "Hello World" );
158 }
159
160 {
161 Value left = 42.0;
162 Value right = std::string(" items");
164 BOOST_CHECK_EQUAL( std::get<std::string>( result ), "42 items" );
165 }
166}
167
171BOOST_AUTO_TEST_CASE( NodeCreation )
172{
173 // Test number node
174 {
175 auto node = NODE::CreateNumber( 42.5 );
176 BOOST_CHECK( node->type == NodeType::Number );
177 BOOST_CHECK_CLOSE( std::get<double>( node->data ), 42.5, 0.001 );
178 }
179
180 // Test string node
181 {
182 auto node = NODE::CreateString( "Hello World" );
183 BOOST_CHECK( node->type == NodeType::String );
184 BOOST_CHECK_EQUAL( std::get<std::string>( node->data ), "Hello World" );
185 }
186
187 // Test variable node
188 {
189 auto node = NODE::CreateVar( "testVar" );
190 BOOST_CHECK( node->type == NodeType::Var );
191 BOOST_CHECK_EQUAL( std::get<std::string>( node->data ), "testVar" );
192 }
193
194 // Test binary operation node
195 {
196 auto left = NODE::CreateNumber( 10.0 );
197 auto right = NODE::CreateNumber( 5.0 );
198 auto binOp = NODE::CreateBinOp( std::move( left ), '+', std::move( right ) );
199
200 BOOST_CHECK( binOp->type == NodeType::BinOp );
201 const auto& binOpData = std::get<BIN_OP_DATA>( binOp->data );
202 BOOST_CHECK( binOpData.op == '+' );
203 BOOST_CHECK( binOpData.left != nullptr );
204 BOOST_CHECK( binOpData.right != nullptr );
205 }
206
207 // Test function node
208 {
209 std::vector<std::unique_ptr<NODE>> args;
210 args.push_back( NODE::CreateNumber( 5.0 ) );
211
212 auto funcNode = NODE::CreateFunction( "abs", std::move( args ) );
213 BOOST_CHECK( funcNode->type == NodeType::Function );
214
215 const auto& funcData = std::get<FUNC_DATA>( funcNode->data );
216 BOOST_CHECK_EQUAL( funcData.name, "abs" );
217 BOOST_CHECK_EQUAL( funcData.args.size(), 1 );
218 }
219}
220
224BOOST_AUTO_TEST_CASE( EvaluationVisitor )
225{
226 ERROR_COLLECTOR errors;
227 auto varResolver = CreateTestVariableResolver();
228 calc_parser::EVAL_VISITOR evaluator( varResolver, errors );
229
230 // Test number evaluation
231 {
232 auto node = NODE::CreateNumber( 42.5 );
233 auto result = node->Accept( evaluator );
234
235 BOOST_CHECK( result.HasValue() );
236 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
237 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 42.5, 0.001 );
238 }
239
240 // Test string evaluation
241 {
242 auto node = NODE::CreateString( "Hello" );
243 auto result = node->Accept( evaluator );
244
245 BOOST_CHECK( result.HasValue() );
246 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
247 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "Hello" );
248 }
249
250 // Test variable evaluation
251 {
252 auto node = NODE::CreateVar( "x" );
253 auto result = node->Accept( evaluator );
254
255 BOOST_CHECK( result.HasValue() );
256 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
257 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 10.0, 0.001 );
258 }
259
260 // Test undefined variable
261 {
262 auto node = NODE::CreateVar( "undefined" );
263 auto result = node->Accept( evaluator );
264
265 BOOST_CHECK( result.HasError() );
266 }
267
268 // Test binary operation
269 {
270 auto left = NODE::CreateNumber( 10.0 );
271 auto right = NODE::CreateNumber( 5.0 );
272 auto binOp = NODE::CreateBinOp( std::move( left ), '+', std::move( right ) );
273
274 auto result = binOp->Accept( evaluator );
275
276 BOOST_CHECK( result.HasValue() );
277 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 15.0, 0.001 );
278 }
279
280 // Test string concatenation with +
281 {
282 auto left = NODE::CreateString( "Hello " );
283 auto right = NODE::CreateString( "World" );
284 auto binOp = NODE::CreateBinOp( std::move( left ), '+', std::move( right ) );
285
286 auto result = binOp->Accept( evaluator );
287
288 BOOST_CHECK( result.HasValue() );
289 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
290 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "Hello World" );
291 }
292}
293
297BOOST_AUTO_TEST_CASE( FunctionEvaluation )
298{
299 ERROR_COLLECTOR errors;
300 auto varResolver = CreateTestVariableResolver();
301 calc_parser::EVAL_VISITOR evaluator( varResolver, errors );
302
303 // Test abs function
304 {
305 std::vector<std::unique_ptr<NODE>> args;
306 args.push_back( NODE::CreateNumber( -5.0 ) );
307
308 auto funcNode = NODE::CreateFunction( "abs", std::move( args ) );
309 auto result = funcNode->Accept( evaluator );
310
311 BOOST_CHECK( result.HasValue() );
312 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 5.0, 0.001 );
313 }
314
315 // Test sqrt function
316 {
317 std::vector<std::unique_ptr<NODE>> args;
318 args.push_back( NODE::CreateNumber( 16.0 ) );
319
320 auto funcNode = NODE::CreateFunction( "sqrt", std::move( args ) );
321 auto result = funcNode->Accept( evaluator );
322
323 BOOST_CHECK( result.HasValue() );
324 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 4.0, 0.001 );
325 }
326
327 // Test sqrt with negative number (should error)
328 {
329 std::vector<std::unique_ptr<NODE>> args;
330 args.push_back( NODE::CreateNumber( -1.0 ) );
331
332 auto funcNode = NODE::CreateFunction( "sqrt", std::move( args ) );
333 auto result = funcNode->Accept( evaluator );
334
335 BOOST_CHECK( result.HasError() );
336 }
337
338 // Test max function
339 {
340 std::vector<std::unique_ptr<NODE>> args;
341 args.push_back( NODE::CreateNumber( 3.0 ) );
342 args.push_back( NODE::CreateNumber( 7.0 ) );
343 args.push_back( NODE::CreateNumber( 1.0 ) );
344
345 auto funcNode = NODE::CreateFunction( "max", std::move( args ) );
346 auto result = funcNode->Accept( evaluator );
347
348 BOOST_CHECK( result.HasValue() );
349 BOOST_CHECK_CLOSE( std::get<double>( result.GetValue() ), 7.0, 0.001 );
350 }
351
352 // Test string function - upper
353 {
354 std::vector<std::unique_ptr<NODE>> args;
355 args.push_back( NODE::CreateString( "hello" ) );
356
357 auto funcNode = NODE::CreateFunction( "upper", std::move( args ) );
358 auto result = funcNode->Accept( evaluator );
359
360 BOOST_CHECK( result.HasValue() );
361 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
362 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "HELLO" );
363 }
364
365 // Test format function
366 {
367 std::vector<std::unique_ptr<NODE>> args;
368 args.push_back( NODE::CreateNumber( 3.14159 ) );
369 args.push_back( NODE::CreateNumber( 2.0 ) );
370
371 auto funcNode = NODE::CreateFunction( "format", std::move( args ) );
372 auto result = funcNode->Accept( evaluator );
373
374 BOOST_CHECK( result.HasValue() );
375 BOOST_CHECK( std::holds_alternative<std::string>( result.GetValue() ) );
376 BOOST_CHECK_EQUAL( std::get<std::string>( result.GetValue() ), "3.14" );
377 }
378
379 // Test unknown function
380 {
381 std::vector<std::unique_ptr<NODE>> args;
382 args.push_back( NODE::CreateNumber( 1.0 ) );
383
384 auto funcNode = NODE::CreateFunction( "unknownfunc", std::move( args ) );
385 auto result = funcNode->Accept( evaluator );
386
387 BOOST_CHECK( result.HasError() );
388 }
389
390 // Test zero-argument functions
391 {
392 std::vector<std::unique_ptr<NODE>> args; // Empty args
393
394 auto todayNode = NODE::CreateFunction( "today", std::move( args ) );
395 auto result = todayNode->Accept( evaluator );
396
397 BOOST_CHECK( result.HasValue() );
398 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
399 // Should return a reasonable number of days since epoch
400 auto days = std::get<double>( result.GetValue() );
401 BOOST_CHECK_GT( days, 18000 ); // Should be after year 2019
402 }
403
404 {
405 std::vector<std::unique_ptr<NODE>> args; // Empty args
406
407 auto randomNode = NODE::CreateFunction( "random", std::move( args ) );
408 auto result = randomNode->Accept( evaluator );
409
410 BOOST_CHECK( result.HasValue() );
411 BOOST_CHECK( std::holds_alternative<double>( result.GetValue() ) );
412 auto randomVal = std::get<double>( result.GetValue() );
413 BOOST_CHECK_GE( randomVal, 0.0 );
414 BOOST_CHECK_LT( randomVal, 1.0 );
415 }
416}
417
421BOOST_AUTO_TEST_CASE( DocumentProcessor )
422{
423 auto varResolver = CreateTestVariableResolver();
424
425 // Create a simple document with text and calculations
426 auto doc = std::make_unique<DOC>();
427
428 // Add text node
429 doc->AddNode( NODE::CreateText( "Value is " ) );
430
431 // Add calculation node
432 auto calcExpr = NODE::CreateBinOp(
433 NODE::CreateNumber( 2.0 ),
434 '+',
435 NODE::CreateNumber( 3.0 )
436 );
437 doc->AddNode( NODE::CreateCalc( std::move( calcExpr ) ) );
438
439 // Add more text
440 doc->AddNode( NODE::CreateText( " units" ) );
441
442 // Process the document
443 auto [result, hadErrors] = calc_parser::DOC_PROCESSOR::Process( *doc, varResolver );
444
445 BOOST_CHECK( !hadErrors );
446 BOOST_CHECK_EQUAL( result, "Value is 5 units" );
447}
448
452BOOST_AUTO_TEST_CASE( ErrorHandling )
453{
454 ERROR_COLLECTOR errors;
455
456 // Test adding errors
457 errors.AddError( "Test error 1" );
458 errors.AddWarning( "Test warning 1" );
459 errors.AddError( "Test error 2" );
460
461 BOOST_CHECK( errors.HasErrors() );
462 BOOST_CHECK( errors.HasWarnings() );
463
464 const auto& errorList = errors.GetErrors();
465 BOOST_CHECK_EQUAL( errorList.size(), 2 );
466 BOOST_CHECK_EQUAL( errorList[0], "Test error 1" );
467 BOOST_CHECK_EQUAL( errorList[1], "Test error 2" );
468
469 const auto& warningList = errors.GetWarnings();
470 BOOST_CHECK_EQUAL( warningList.size(), 1 );
471 BOOST_CHECK_EQUAL( warningList[0], "Test warning 1" );
472
473 // Test error message formatting
474 auto allMessages = errors.GetAllMessages();
475 BOOST_CHECK( allMessages.find( "Error: Test error 1" ) != std::string::npos );
476 BOOST_CHECK( allMessages.find( "Warning: Test warning 1" ) != std::string::npos );
477
478 // Test clearing
479 errors.Clear();
480 BOOST_CHECK( !errors.HasErrors() );
481 BOOST_CHECK( !errors.HasWarnings() );
482}
483
488{
489 // Test string token
490 {
491 auto token = MakeStringToken( "Hello World" );
492 BOOST_CHECK( token.isString );
493 BOOST_CHECK_EQUAL( GetTokenString( token ), "Hello World" );
494 }
495
496 // Test number token
497 {
498 auto token = MakeNumberToken( 42.5 );
499 BOOST_CHECK( !token.isString );
500 BOOST_CHECK_CLOSE( GetTokenDouble( token ), 42.5, 0.001 );
501 }
502}
503
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.