KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_text_eval_numeric_compat.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#include <wx/wxcrt.h>
30
31// Make EDA_UNITS printable for Boost.Test
32std::ostream& operator<<( std::ostream& aStream, EDA_UNITS aUnits )
33{
34 wxString unitStr = EDA_UNIT_UTILS::GetText( aUnits );
35 return aStream << unitStr.ToStdString();
36}
37
41BOOST_AUTO_TEST_SUITE( TextEvalNumericCompat )
42
43
47{
48 wxString input; // Input expression wrapped in @{} for text_eval
49 wxString exp_result; // Expected result as string
50 bool shouldError; // Whether this case should produce an error
51};
52
57{
58 EXPRESSION_EVALUATOR evaluator;
59
60 wxString result = evaluator.Evaluate("@{1}");
62 BOOST_CHECK( !evaluator.HasErrors() );
63}
64
69{
70 EXPRESSION_EVALUATOR evaluator;
71
72 // Set variable and test usage
73 evaluator.SetVariable( "MoL", 42.0 );
74 wxString result = evaluator.Evaluate( "@{1 + ${MoL}}" );
76 BOOST_CHECK( !evaluator.HasErrors() );
77
78 // Change variable value
79 evaluator.SetVariable( "MoL", 422.0 );
80 result = evaluator.Evaluate( "@{1 + ${MoL}}" );
81 BOOST_CHECK_EQUAL( result, "423" );
82 BOOST_CHECK( !evaluator.HasErrors() );
83
84 // Add another variable
85 evaluator.SetVariable( "pi", 3.14 );
86 BOOST_CHECK( evaluator.HasVariable( "pi" ) );
87
88 // Remove one variable
89 BOOST_CHECK( evaluator.RemoveVariable( "pi" ) );
90 BOOST_CHECK( !evaluator.HasVariable( "pi" ) );
91
92 // Other variable should still be there
93 BOOST_CHECK( evaluator.HasVariable( "MoL" ) );
94
95 // Add another variable back
96 evaluator.SetVariable( "piish", 3.1 );
97
98 // Test multiple variables
99 result = evaluator.Evaluate( "@{1 + ${MoL} + ${piish}}" );
100 BOOST_CHECK_EQUAL( result, "426.1" );
101 BOOST_CHECK( !evaluator.HasErrors() );
102
103 // Clear all variables
104 evaluator.ClearVariables();
105 BOOST_CHECK( !evaluator.HasVariable( "MoL" ) );
106 BOOST_CHECK( !evaluator.HasVariable( "piish" ) );
107}
108
113static const std::vector<TEXT_EVAL_CASE> eval_cases_valid = {
114 // Empty case - text_eval handles this differently than numeric evaluator
115 { "@{}", "@{}", true }, // Empty expressions should error in text_eval
116
117 // Trivial eval
118 { "@{1}", "1", false },
119
120 // Decimal separators (text_eval may handle differently)
121 { "@{1.5}", "1.5", false },
122 // Note: comma as decimal separator might not work in text_eval
123
124 // Simple arithmetic
125 { "@{1+2}", "3", false },
126 { "@{1 + 2}", "3", false },
127 { "@{1.5 + 0.2 + 0.1}", "1.8", false },
128 { "@{3 - 10}", "-7", false },
129 { "@{1 + 2 + 10 + 1000.05}", "1013.05", false },
130
131 // Operator precedence
132 { "@{1 + 2 - 4 * 20 / 2}", "-37", false },
133
134 // Parentheses
135 { "@{(1)}", "1", false },
136 { "@{-(1 + (2 - 4)) * 20.8 / 2}", "10.4", false },
137
138 // Unary operators
139 { "@{+2 - 1}", "1", false },
140};
141
145static const std::vector<TEXT_EVAL_CASE> eval_cases_invalid = {
146 // Trailing operator
147 { "@{1+}", "", true },
148
149 // Leading operator (except unary)
150 { "@{*2 + 1}", "", true },
151
152 // Division by zero
153 { "@{1 / 0}", "", true },
154
155 // Unknown variables should preserve the original expression
156 { "@{1 + ${unknown}}", "@{1 + ${unknown}}", true },
157
158 // Mismatched parentheses
159 { "@{(1 + 2}", "", true },
160 { "@{1 + 2)}", "", true },
161
162 // Invalid syntax
163 { "@{1 $ 2}", "", true },
164};
165
169BOOST_AUTO_TEST_CASE( ValidResults )
170{
171 EXPRESSION_EVALUATOR evaluator;
172
173 for( const auto& testCase : eval_cases_valid )
174 {
175 BOOST_TEST_CONTEXT( testCase.input + " -> " + testCase.exp_result )
176 {
177 wxString result = evaluator.Evaluate( testCase.input );
178
179 if( testCase.shouldError )
180 {
181 BOOST_CHECK( evaluator.HasErrors() );
182 }
183 else
184 {
185 BOOST_CHECK( !evaluator.HasErrors() );
186 BOOST_CHECK_EQUAL( result, testCase.exp_result );
187 }
188 }
189 }
190}
191
195BOOST_AUTO_TEST_CASE( InvalidResults )
196{
197 EXPRESSION_EVALUATOR evaluator;
198
199 for( const auto& testCase : eval_cases_invalid )
200 {
201 BOOST_TEST_CONTEXT( testCase.input )
202 {
203 wxString result = evaluator.Evaluate( testCase.input );
204
205 // All these cases should produce errors
206 BOOST_CHECK( evaluator.HasErrors() );
207
208 // For undefined variables, result should be the original expression
209 if( testCase.input.Contains( "${unknown}" ) )
210 {
211 BOOST_CHECK_EQUAL( result, testCase.input );
212 }
213 }
214 }
215}
216
220BOOST_AUTO_TEST_CASE( VariableExpressions )
221{
222 EXPRESSION_EVALUATOR evaluator;
223
224 // Set up variables similar to numeric evaluator tests
225 evaluator.SetVariable( "x", 10.0 );
226 evaluator.SetVariable( "y", 5.0 );
227
228 struct VarTestCase {
229 wxString input;
230 wxString expected;
231 bool shouldError;
232 };
233
234 const std::vector<VarTestCase> varCases = {
235 { "@{${x}}", "10", false },
236 { "@{${y}}", "5", false },
237 { "@{${x} + ${y}}", "15", false },
238 { "@{${x} * ${y}}", "50", false },
239 { "@{${x} - ${y}}", "5", false },
240 { "@{${x} / ${y}}", "2", false },
241 { "@{(${x} + ${y}) * 2}", "30", false },
242
243 // Undefined variable should preserve expression
244 { "@{${undefined}}", "@{${undefined}}", true },
245
246 // Mixed defined and undefined
247 { "@{${x} + ${undefined}}", "@{${x} + ${undefined}}", true },
248 };
249
250 for( const auto& testCase : varCases )
251 {
252 BOOST_TEST_CONTEXT( testCase.input + " -> " + testCase.expected )
253 {
254 wxString result = evaluator.Evaluate( testCase.input );
255
256 if( testCase.shouldError )
257 {
258 BOOST_CHECK( evaluator.HasErrors() );
259 BOOST_CHECK_EQUAL( result, testCase.input ); // Original expression preserved
260 }
261 else
262 {
263 BOOST_CHECK( !evaluator.HasErrors() );
264 BOOST_CHECK_EQUAL( result, testCase.expected );
265 }
266 }
267 }
268}
269
273BOOST_AUTO_TEST_CASE( MathFunctions )
274{
275 EXPRESSION_EVALUATOR evaluator;
276
277 struct MathTestCase {
278 wxString input;
279 wxString expected;
280 bool shouldError;
281 };
282
283 const std::vector<MathTestCase> mathCases = {
284 // Basic math functions that are confirmed to work
285 { "@{abs(-5)}", "5", false },
286 { "@{min(3, 7)}", "3", false },
287 { "@{max(3, 7)}", "7", false },
288 { "@{sqrt(16)}", "4", false },
289 { "@{ceil(3.2)}", "4", false },
290 { "@{floor(3.8)}", "3", false },
291 { "@{round(3.6)}", "4", false },
292 { "@{pow(2, 3)}", "8", false },
293
294 // Sum and average functions
295 { "@{sum(1, 2, 3)}", "6", false },
296 { "@{avg(2, 4, 6)}", "4", false },
297 };
298
299 for( const auto& testCase : mathCases )
300 {
301 BOOST_TEST_CONTEXT( testCase.input + " -> " + testCase.expected )
302 {
303 wxString result = evaluator.Evaluate( testCase.input );
304
305 if( testCase.shouldError )
306 {
307 BOOST_CHECK( evaluator.HasErrors() );
308 }
309 else
310 {
311 BOOST_CHECK( !evaluator.HasErrors() );
312 BOOST_CHECK_EQUAL( result, testCase.expected );
313 }
314 }
315 }
316}
317
322{
323 // Test basic unit constructor
324 EXPRESSION_EVALUATOR evaluator_mm( EDA_UNITS::MM );
325 EXPRESSION_EVALUATOR evaluator_inch( EDA_UNITS::INCH );
326 EXPRESSION_EVALUATOR evaluator_mil( EDA_UNITS::MILS );
327
328 // Test unit setting and getting
332
333 // Test unit change
334 evaluator_mm.SetDefaultUnits( EDA_UNITS::INCH );
336
337 // Test basic expressions work with unit-aware evaluator
338 wxString result = evaluator_mm.Evaluate( "@{1 + 2}" );
340 BOOST_CHECK( !evaluator_mm.HasErrors() );
341
342 // Test unit constructor with variable callback
343 auto callback = [](const std::string& varName) -> calc_parser::Result<calc_parser::Value> {
344 if (varName == "width") {
346 }
347 return calc_parser::MakeError<calc_parser::Value>("Variable not found: " + varName);
348 };
349
350 EXPRESSION_EVALUATOR evaluator_callback( EDA_UNITS::MM, callback, false );
351 BOOST_CHECK_EQUAL( evaluator_callback.GetDefaultUnits(), EDA_UNITS::MM );
352 BOOST_CHECK( evaluator_callback.HasVariableCallback() );
353
354 result = evaluator_callback.Evaluate( "@{${width} * 2}" );
355 BOOST_CHECK_EQUAL( result, "20" );
356 BOOST_CHECK( !evaluator_callback.HasErrors() );
357}
358
363BOOST_AUTO_TEST_CASE( UnitInfrastructureReadiness )
364{
365 // Test that different unit types can be set and retrieved
366 EXPRESSION_EVALUATOR evaluator_mm( EDA_UNITS::MM );
367 EXPRESSION_EVALUATOR evaluator_inch( EDA_UNITS::INCH );
368 EXPRESSION_EVALUATOR evaluator_mil( EDA_UNITS::MILS );
369 EXPRESSION_EVALUATOR evaluator_cm( EDA_UNITS::CM );
370 EXPRESSION_EVALUATOR evaluator_um( EDA_UNITS::UM );
371
372 // Verify unit storage works for all supported units
378
379 // Test unit changes
380 evaluator_mm.SetDefaultUnits( EDA_UNITS::INCH );
382
383 evaluator_inch.SetDefaultUnits( EDA_UNITS::MILS );
385
386 // Verify expressions still work with all unit types
387 wxString result;
388
389 result = evaluator_mm.Evaluate( "@{5 * 2}" );
390 BOOST_CHECK_EQUAL( result, "10" );
391
392 result = evaluator_inch.Evaluate( "@{3.5 + 1.5}" );
394
395 result = evaluator_mil.Evaluate( "@{100 / 4}" );
396 BOOST_CHECK_EQUAL( result, "25" );
397
398 // Test complex expressions work with unit-aware evaluators
399 result = evaluator_cm.Evaluate( "@{(10 + 5) * 2 - 1}" );
400 BOOST_CHECK_EQUAL( result, "29" );
401
402 // Test variable support with units
403 evaluator_um.SetVariable( "length", 25.4 );
404 result = evaluator_um.Evaluate( "@{${length} * 2}" );
405 BOOST_CHECK_EQUAL( result, "50.8" );
406
407 // Test that unit-aware evaluator preserves its unit setting across operations
408 EXPRESSION_EVALUATOR persistent_eval( EDA_UNITS::MILS );
410
411 persistent_eval.Evaluate( "@{1 + 1}" );
413
414 persistent_eval.SetVariable( "test", 42 );
416
417 persistent_eval.Evaluate( "@{${test} + 8}" );
419 BOOST_CHECK_EQUAL( persistent_eval.Evaluate( "@{${test} + 8}" ), "50" );
420}
421
426BOOST_AUTO_TEST_CASE( UnitMixingExpectations )
427{
428 EXPRESSION_EVALUATOR evaluator_mm( EDA_UNITS::MM );
429
430 // Verify basic functionality works before discussing unit mixing
432
433 wxString result = evaluator_mm.Evaluate( "@{2 + 3}" );
435
436 // Test complex arithmetic
437 result = evaluator_mm.Evaluate( "@{(1 + 2) * 3}" );
439
440 // Test with decimals (important for unit conversions)
441 result = evaluator_mm.Evaluate( "@{25.4 + 12.7}" );
442 // Use close comparison for floating point
443 double numeric_result = wxAtof( result );
444 BOOST_CHECK_CLOSE( numeric_result, 38.1, 0.01 );
445
446 // Test with variables that could represent converted values
447 evaluator_mm.SetVariable( "inch_in_mm", 25.4 ); // 1 inch = 25.4 mm
448 evaluator_mm.SetVariable( "mil_in_mm", 0.0254 ); // 1 mil = 0.0254 mm
449
450 result = evaluator_mm.Evaluate( "@{${inch_in_mm} + ${mil_in_mm}}" );
451 BOOST_CHECK_EQUAL( result, "25.4254" );
452
453 // Simulate what "1mm + 1in" should become when units are parsed
454 evaluator_mm.SetVariable( "mm_part", 1.0 );
455 evaluator_mm.SetVariable( "in_part", 25.4 ); // 1in converted to mm
456 result = evaluator_mm.Evaluate( "@{${mm_part} + ${in_part}}" );
457 BOOST_CHECK_EQUAL( result, "26.4" );
458
459 // Simulate what "1in + 1000mil" should become
460 evaluator_mm.SetVariable( "one_inch", 25.4 );
461 evaluator_mm.SetVariable( "thousand_mils", 25.4 ); // 1000 mils = 1 inch = 25.4 mm
462 result = evaluator_mm.Evaluate( "@{${one_inch} + ${thousand_mils}}" );
463 BOOST_CHECK_EQUAL( result, "50.8" );
464
465 // Test expressions that will be possible once unit parsing is integrated:
466 // These would parse "1mm", "1in", "1mil" etc. and convert to default units
467
468
469 // Basic unit expressions
470 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{1mm}" ), "1" );
471 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{1in}" ), "25.4" );
472 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{1000mil}" ), "25.4" );
473
474 // Mixed unit arithmetic
475 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{1mm + 1in}" ), "26.4" );
476 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{1in + 1000mil}" ), "50.8" );
477 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{10mm + 0.5in + 500mil}" ), "35.4" );
478
479 // Unit expressions with whitespace
480 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{1 mm}" ), "1" );
481 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{1 in}" ), "25.4" );
482
483 // Complex mixed unit expressions with variables
484 evaluator_mm.SetVariable( "width", 10 ); // 10mm
485 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{${width}mm + 1in}" ), "35.4" );
486 // These two should both work the same
487 BOOST_CHECK_EQUAL( evaluator_mm.Evaluate( "@{${width} * 1mm + 1in}" ), "35.4" );
488
489 // Different evaluator units should convert appropriately
490 EXPRESSION_EVALUATOR evaluator_inch( EDA_UNITS::INCH );
491 BOOST_CHECK_EQUAL( evaluator_inch.Evaluate( "@{1in}" ), "1" );
492 BOOST_CHECK_EQUAL( evaluator_inch.Evaluate( "@{25.4mm}" ), "1" );
493}
494
498BOOST_AUTO_TEST_CASE( ActualUnitParsing )
499{
500 // Test MM evaluator with unit expressions
501 EXPRESSION_EVALUATOR evaluator_mm( EDA_UNITS::MM );
503
504 // Test basic unit expressions (these should now work!)
505 wxString result;
506
507 // Debug: Test basic arithmetic first
508 result = evaluator_mm.Evaluate( "@{2+3}" );
510
511 // Debug: Test just number
512 result = evaluator_mm.Evaluate( "@{1}" );
514
515 // Debug: Test the working case
516 result = evaluator_mm.Evaluate( "@{1in}" );
517 if (result != "25.4") {
518 std::cout << "DEBUG: @{1in} returned '" << result.ToStdString() << "'" << std::endl;
519 if (evaluator_mm.HasErrors()) {
520 std::cout << "DEBUG: @{1in} Errors: " << evaluator_mm.GetErrorSummary().ToStdString() << std::endl;
521 }
522 }
523 BOOST_CHECK_EQUAL( result, "25.4" );
524
525 result = evaluator_mm.Evaluate( "@{1mil}" );
526 BOOST_CHECK_EQUAL( result, "0.0254" );
527
528 result = evaluator_mm.Evaluate( "@{1mm}" );
530
531 // 1 inch should convert to 25.4 mm
532 result = evaluator_mm.Evaluate( "@{1in}" );
533 BOOST_CHECK_EQUAL( result, "25.4" );
534
535 // 1000 mils should convert to 25.4 mm (1000 mils = 1 inch)
536 result = evaluator_mm.Evaluate( "@{1000mil}" );
537 BOOST_CHECK_EQUAL( result, "25.4" );
538
539 // Test mixed unit arithmetic
540 result = evaluator_mm.Evaluate( "@{1mm + 1in}" );
541 BOOST_CHECK_EQUAL( result, "26.4" );
542
543 result = evaluator_mm.Evaluate( "@{1in + 1000mil}" );
544 BOOST_CHECK_EQUAL( result, "50.8" );
545
546 // Test more complex expressions
547 result = evaluator_mm.Evaluate( "@{10mm + 0.5in + 500mil}" );
548 BOOST_CHECK_EQUAL( result, "35.4" );
549
550 // Test unit expressions with spaces (if supported)
551 result = evaluator_mm.Evaluate( "@{1 mm}" );
553
554 // Test with different default units
555 EXPRESSION_EVALUATOR evaluator_inch( EDA_UNITS::INCH );
556
557 // 1 inch should be 1 when default unit is inches
558 result = evaluator_inch.Evaluate( "@{1in}" );
560
561 // 25.4mm should convert to 1 inch (with floating point tolerance)
562 result = evaluator_inch.Evaluate( "@{25.4mm}" );
563 // Use approximate comparison for floating point
564 double result_val = wxAtof(result);
565 BOOST_CHECK( std::abs(result_val - 1.0) < 0.001 );
566
567 // Test arithmetic with inch evaluator
568 result = evaluator_inch.Evaluate( "@{1in + 1000mil}" );
569 BOOST_CHECK_EQUAL( result, "2" ); // 1 inch + 1 inch = 2 inches
570
571 // Test centimeters
572 result = evaluator_mm.Evaluate( "@{1cm}" );
573 BOOST_CHECK_EQUAL( result, "10" ); // 1 cm = 10 mm
574
575 // Test micrometers
576 result = evaluator_mm.Evaluate( "@{1000um}" );
577 BOOST_CHECK_EQUAL( result, "1" ); // 1000 um = 1 mm
578
579 // Test quotes for inches
580 result = evaluator_mm.Evaluate( "@{1\"}" );
581 BOOST_CHECK_EQUAL( result, "25.4" ); // 1" = 25.4 mm
582
583 // Test complex mixed expressions with parentheses
584 result = evaluator_mm.Evaluate( "@{(1in + 500mil) * 2}" );
585 // Expected: (25.4 + 12.7) * 2 = 38.1 * 2 = 76.2mm
586 double result_val2 = wxAtof(result);
587 BOOST_CHECK( std::abs(result_val2 - 76.2) < 0.001 );
588}
589
593BOOST_AUTO_TEST_CASE( UnitParsingEdgeCases, * boost::unit_test::enabled() )
594{
595 EXPRESSION_EVALUATOR evaluator_mm( EDA_UNITS::MM );
596
597 // Test invalid units (should be treated as plain numbers)
598 wxString result = evaluator_mm.Evaluate( "@{1xyz}" );
599 // Should parse as "1" followed by identifier "xyz", might be an error or treat as 1
600 // This behavior depends on implementation details
601
602 // Test numbers without units (should work normally)
603 result = evaluator_mm.Evaluate( "@{25.4}" );
605
606 // Test zero with units
607 result = evaluator_mm.Evaluate( "@{0mm}" );
609
610 result = evaluator_mm.Evaluate( "@{0in}" );
612
613 // Test decimal values with units
614 result = evaluator_mm.Evaluate( "@{2.54cm}" );
615 BOOST_CHECK_EQUAL( result, "25.4" ); // 2.54 cm = 25.4 mm
616
617 result = evaluator_mm.Evaluate( "@{0.5in}" );
618 BOOST_CHECK_EQUAL( result, "12.7" ); // 0.5 inch = 12.7 mm
619
620 // Test very small values
621 result = evaluator_mm.Evaluate( "@{1um}" );
622 BOOST_CHECK_EQUAL( result, "0.001" ); // 1 um = 0.001 mm
623}
624
625BOOST_AUTO_TEST_CASE( NumericEvaluatorCompatibility )
626{
627 // Test the NUMERIC_EVALUATOR_COMPAT wrapper class that provides
628 // a drop-in replacement for NUMERIC_EVALUATOR using EXPRESSION_EVALUATOR backend
629
631
632 // Test basic arithmetic
633 BOOST_CHECK( eval.Process( "1 + 2" ) );
634 BOOST_CHECK( eval.IsValid() );
635 BOOST_CHECK_EQUAL( eval.Result(), "3" );
636 BOOST_CHECK_EQUAL( eval.OriginalText(), "1 + 2" );
637
638 // Test variables
639 eval.SetVar( "x", 5.0 );
640 eval.SetVar( "y", 3.0 );
641
642 BOOST_CHECK( eval.Process( "x + y" ) );
643 BOOST_CHECK( eval.IsValid() );
644 BOOST_CHECK_EQUAL( eval.Result(), "8" );
645
646 // Test GetVar
647 BOOST_CHECK_CLOSE( eval.GetVar( "x" ), 5.0, 0.001 );
648 BOOST_CHECK_CLOSE( eval.GetVar( "y" ), 3.0, 0.001 );
649 BOOST_CHECK_CLOSE( eval.GetVar( "undefined" ), 0.0, 0.001 );
650
651 // Test units (should work seamlessly)
652 BOOST_CHECK( eval.Process( "1in + 1mm" ) );
653 BOOST_CHECK( eval.IsValid() );
654 BOOST_CHECK_EQUAL( eval.Result(), "26.4" ); // 1 inch + 1mm in mm
655
656 // Test mathematical functions
657 BOOST_CHECK( eval.Process( "sqrt(16)" ) );
658 BOOST_CHECK( eval.IsValid() );
659 BOOST_CHECK_EQUAL( eval.Result(), "4" );
660
661 // Test invalid expression - use something clearly invalid
662 BOOST_CHECK( !eval.Process( "1 + * 2" ) ); // Clearly invalid: two operators in a row
663 BOOST_CHECK( !eval.IsValid() );
664
665 // Test Clear() - should reset state but keep variables
666 eval.Clear();
667 BOOST_CHECK_CLOSE( eval.GetVar( "x" ), 5.0, 0.001 ); // Variables should still be there
668
669 BOOST_CHECK( eval.Process( "x * 2" ) );
670 BOOST_CHECK( eval.IsValid() );
671 BOOST_CHECK_EQUAL( eval.Result(), "10" );
672
673 // Test variable removal
674 eval.RemoveVar( "x" );
675 BOOST_CHECK_CLOSE( eval.GetVar( "x" ), 0.0, 0.001 ); // Should be 0.0 for undefined
676 BOOST_CHECK_CLOSE( eval.GetVar( "y" ), 3.0, 0.001 ); // y should still exist
677
678 // Test ClearVar()
679 eval.ClearVar();
680 BOOST_CHECK_CLOSE( eval.GetVar( "y" ), 0.0, 0.001 ); // All variables should be gone
681
682 // Test that we can still use the evaluator after clearing
683 BOOST_CHECK( eval.Process( "42" ) );
684 BOOST_CHECK( eval.IsValid() );
685 BOOST_CHECK_EQUAL( eval.Result(), "42" );
686
687 // Test LocaleChanged() - should be a no-op but not crash
688 eval.LocaleChanged();
689 BOOST_CHECK( eval.Process( "3.14" ) );
690 BOOST_CHECK( eval.IsValid() );
691 BOOST_CHECK_EQUAL( eval.Result(), "3.14" );
692}
693
699BOOST_AUTO_TEST_CASE( UnitParsingPriority )
700{
701 EXPRESSION_EVALUATOR evaluator_mm( EDA_UNITS::MM );
702 wxString result;
703
704 // Test "mil" unit (thousandths of an inch)
705 // 40mil should convert to 1.016mm (40 * 0.0254)
706 result = evaluator_mm.Evaluate( "@{40mil}" );
707 BOOST_CHECK_CLOSE( wxAtof( result ), 1.016, 0.01 ); // Allow 0.01% tolerance
708
709 // Test "mm" unit (should not be affected by 'm' SI prefix)
710 result = evaluator_mm.Evaluate( "@{10mm}" );
711 BOOST_CHECK_CLOSE( wxAtof( result ), 10.0, 0.01 );
712
713 // Test "um" unit (micrometers, should not be parsed as 'u' prefix + 'm')
714 // 1000um should equal 1mm
715 result = evaluator_mm.Evaluate( "@{1000um}" );
716 BOOST_CHECK_CLOSE( wxAtof( result ), 1.0, 0.01 );
717
718 // Test combined expression with multiple unit types
719 // 1mm + 40mil should equal 2.016mm
720 result = evaluator_mm.Evaluate( "@{1mm + 40mil}" );
721 BOOST_CHECK_CLOSE( wxAtof( result ), 2.016, 0.01 );
722
723 // Test that SI prefixes still work when not part of a unit
724 // 1k should equal 1000
725 result = evaluator_mm.Evaluate( "@{1k}" );
726 BOOST_CHECK_CLOSE( wxAtof( result ), 1000.0, 0.01 );
727
728 // Test that 'm' SI prefix works when followed by non-unit characters
729 // 40m should equal 0.04 (40 milli)
730 result = evaluator_mm.Evaluate( "@{40m}" );
731 BOOST_CHECK_CLOSE( wxAtof( result ), 0.04, 0.01 );
732
733 // Test "thou" unit (alternative name for mil)
734 // 100thou should equal 2.54mm
735 result = evaluator_mm.Evaluate( "@{100thou}" );
736 BOOST_CHECK_CLOSE( wxAtof( result ), 2.54, 0.01 );
737}
738
High-level wrapper for evaluating mathematical and string expressions in wxString format.
EDA_UNITS GetDefaultUnits() const
Get the current default units.
bool HasVariableCallback() const
Check if a custom variable callback is set.
wxString Evaluate(const wxString &aInput)
Main evaluation function - processes input string and evaluates all} expressions.
bool RemoveVariable(const wxString &aName)
Remove a variable from the evaluator.
bool HasErrors() const
Check if the last evaluation had errors.
wxString GetErrorSummary() const
Get detailed error information from the last evaluation.
void SetDefaultUnits(EDA_UNITS aUnits)
Set the default units for expressions.
void ClearVariables()
Clear all stored variables.
bool HasVariable(const wxString &aName) const
Check if a variable exists in stored variables.
void SetVariable(const wxString &aName, double aValue)
Set a numeric variable for use in expressions.
NUMERIC_EVALUATOR compatible wrapper around EXPRESSION_EVALUATOR.
void LocaleChanged()
Handle locale changes (for decimal separator)
bool Process(const wxString &aString)
Process and evaluate an expression.
wxString Result() const
Get the result of the last evaluation.
void RemoveVar(const wxString &aString)
Remove a single variable.
void SetVar(const wxString &aString, double aValue)
Set a variable value.
void ClearVar()
Remove all variables.
double GetVar(const wxString &aString)
Get a variable value.
void Clear()
Clear parser state but retain variables.
bool IsValid() const
Check if the last evaluation was successful.
wxString OriginalText() const
Get the original input text.
EDA_UNITS
Definition eda_units.h:44
KICOMMON_API wxString GetText(EDA_UNITS aUnits, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Get the units string for a given units type.
auto MakeValue(T aVal) -> Result< T >
auto MakeError(std::string aMsg) -> Result< T >
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
Declare the test suite.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
static const std::vector< EVAL_INVALID_CASE > eval_cases_invalid
A list of invalid test strings.
static const std::vector< EVAL_CASE > eval_cases_valid
A list of valid test strings and the expected results.
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_AUTO_TEST_CASE(Basic)
Basic functionality test.
std::ostream & operator<<(std::ostream &aStream, EDA_UNITS aUnits)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")