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