KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_libeval_compiler.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
21#include <boost/test/data/test_case.hpp>
22
23#include <wx/wx.h>
24
25#include <layer_ids.h>
27#include <drc/drc_rule.h>
28#include <pcbnew/board.h>
30#include <pcbnew/pcb_track.h>
31#include <pcbnew/footprint.h>
32#include <pcbnew/pcb_text.h>
34#include <properties/property.h>
36
37BOOST_AUTO_TEST_SUITE( Libeval_Compiler )
38
40{
41 wxString expression;
44
45 friend std::ostream& operator<<( std::ostream& os, const EXPR_TO_TEST& expr )
46 {
47 os << expr.expression;
48 return os;
49 }
50};
51
53
54const static std::vector<EXPR_TO_TEST> simpleExpressions = {
55 { "10mm + 20 mm", false, VAL( 30e6 ) },
56 { "3*(7+8)", false, VAL( 3 * ( 7 + 8 ) ) },
57 { "3*7+8", false, VAL( 3 * 7 + 8 ) },
58 { "(3*7)+8", false, VAL( 3 * 7 + 8 ) },
59 { "10mm + 20)", true, VAL( 0 ) },
60
61 { "1", false, VAL(1) },
62 { "1.5", false, VAL(1.5) },
63 { "1,5", false, VAL(1.5) },
64 { "1mm", false, VAL(1e6) },
65 // Any White-space is OK
66 { " 1 + 2 ", false, VAL(3) },
67 // Decimals are OK in expressions
68 { "1.5 + 0.2 + 0.1", false, VAL(1.8) },
69 // Negatives are OK
70 { "3 - 10", false, VAL(-7) },
71 // Lots of operands
72 { "1 + 2 + 10 + 1000.05", false, VAL(1013.05) },
73 // Operator precedence
74 { "1 + 2 - 4 * 20 / 2", false, VAL(-37) },
75 // Parens
76 { "(1)", false, VAL(1) },
77 // Parens affect precedence
78 { "-(1 + (2 - 4)) * 20.8 / 2", false, VAL(10.4) },
79 // Unary addition is a sign, not a leading operator
80 { "+2 - 1", false, VAL(1) },
81 // A short-circuited || must yield a normalized 1, not the raw (nonzero) left operand, so a
82 // boolean feeding a further operator behaves the same as the non-short-circuited path.
83 { "(2 || 0) == 1", false, VAL(1) },
84 { "(7 || 0) + 5", false, VAL(6) }
85};
86
87
88const static std::vector<EXPR_TO_TEST> introspectionExpressions = {
89 { "A.type == 'Pad' && B.type == 'Pad' && (A.existsOnLayer('F.Cu'))", false, VAL( 0.0 ) },
90 { "A.Width > B.Width", false, VAL( 0.0 ) },
91 { "A.Width + B.Width", false, VAL( pcbIUScale.MilsToIU(10) + pcbIUScale.MilsToIU(20) ) },
92 { "A.Netclass", false, VAL( "HV_LINE" ) },
93 { "(A.Netclass == 'HV_LINE') && (B.netclass == 'otherClass') && (B.netclass != 'F.Cu')", false,
94 VAL( 1.0 ) },
95 { "A.Netclass + 1.0", false, VAL( 1.0 ) },
96 { "A.hasNetclass('HV_LINE')", false, VAL( 1.0 ) },
97 { "A.hasNetclass('HV_*')", false, VAL( 1.0 ) },
98 { "A.type == 'Track' && B.type == 'Track' && A.layer == 'F.Cu'", false, VAL( 1.0 ) },
99 { "(A.type == 'Track') && (B.type == 'Track') && (A.layer == 'F.Cu')", false, VAL( 1.0 ) },
100 { "A.type == 'Via' && A.isMicroVia()", false, VAL(0.0) }
101};
102
103
104static bool testEvalExpr( const wxString& expr, const LIBEVAL::VALUE& expectedResult,
105 bool expectError = false, BOARD_ITEM* itemA = nullptr,
106 BOARD_ITEM* itemB = nullptr )
107{
108 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
109 PCBEXPR_UCODE ucode;
112 bool ok = true;
113
114 context.SetItems( itemA, itemB );
115
116
117 BOOST_TEST_MESSAGE( "Expr: '" << expr.c_str() << "'" );
118
119 bool error = !compiler.Compile( expr, &ucode, &preflightContext );
120
121 BOOST_CHECK_EQUAL( error, expectError );
122
123 if( error != expectError )
124 {
125 BOOST_TEST_MESSAGE( "Result: FAIL: " << compiler.GetError().message.c_str() <<
126 " (code pos: " << compiler.GetError().srcPos << ")" );
127
128 return false;
129 }
130
131 if( error )
132 return true;
133
135
136 if( ok )
137 {
138 result = ucode.Run( &context );
139 ok = ( result->EqualTo( &context, &expectedResult ) );
140 }
141
142 if( expectedResult.GetType() == LIBEVAL::VT_NUMERIC )
143 {
144 BOOST_CHECK_EQUAL( result->AsDouble(), expectedResult.AsDouble() );
145 }
146 else
147 {
148 BOOST_CHECK_EQUAL( result->AsString(), expectedResult.AsString() );
149 }
150
151 return ok;
152}
153
154
155BOOST_DATA_TEST_CASE( SimpleExpressions, boost::unit_test::data::make( simpleExpressions ), expr )
156{
157 testEvalExpr( expr.expression, expr.expectedResult, expr.expectError );
158}
159
160
161BOOST_AUTO_TEST_CASE( IntrospectedProperties )
162{
164 propMgr.Rebuild();
165
166 BOARD brd;
167
168 const NETINFO_LIST& netInfo = brd.GetNetInfo();
169
170 std::shared_ptr<NETCLASS> netclass1( new NETCLASS( "HV_LINE" ) );
171 std::shared_ptr<NETCLASS> netclass2( new NETCLASS( "otherClass" ) );
172
173 auto net1info = new NETINFO_ITEM( &brd, "net1", 1 );
174 auto net2info = new NETINFO_ITEM( &brd, "net2", 2 );
175
176 net1info->SetNetClass( netclass1 );
177 net2info->SetNetClass( netclass2 );
178
179 PCB_TRACK trackA( &brd );
180 PCB_TRACK trackB( &brd );
181
182 trackA.SetNet( net1info );
183 trackB.SetNet( net2info );
184
185 trackB.SetLayer( F_Cu );
186
187 trackA.SetWidth( pcbIUScale.MilsToIU( 10 ) );
188 trackB.SetWidth( pcbIUScale.MilsToIU( 20 ) );
189
190 for( const auto& expr : introspectionExpressions )
191 {
192 testEvalExpr( expr.expression, expr.expectedResult, expr.expectError, &trackA, &trackB );
193 }
194}
195
196BOOST_AUTO_TEST_CASE( InNetChainClassWildcard )
197{
199 propMgr.Rebuild();
200
201 BOARD brd;
202
203 std::shared_ptr<NET_SETTINGS> netSettings = brd.GetDesignSettings().m_NetSettings;
204 netSettings->SetNetChainClass( wxT( "ChainHS" ), wxT( "HighSpeed" ) );
205
206 auto netUnclassified = new NETINFO_ITEM( &brd, "netA", 1 );
207 auto netClassified = new NETINFO_ITEM( &brd, "netB", 2 );
208 auto netNoChain = new NETINFO_ITEM( &brd, "netC", 3 );
209
210 netUnclassified->SetNetChain( wxT( "ChainOrphan" ) );
211 netClassified->SetNetChain( wxT( "ChainHS" ) );
212
213 PCB_TRACK trackUnclassified( &brd );
214 PCB_TRACK trackClassified( &brd );
215 PCB_TRACK trackNoChain( &brd );
216
217 trackUnclassified.SetNet( netUnclassified );
218 trackClassified.SetNet( netClassified );
219 trackNoChain.SetNet( netNoChain );
220
221 // A chain with no class assignment must not match any inNetChainClass() pattern,
222 // including the '*' wildcard.
223 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackUnclassified,
224 &trackUnclassified );
225 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 0.0 ), false, &trackUnclassified,
226 &trackUnclassified );
227
228 // Net with no chain at all must not match either.
229 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackNoChain,
230 &trackNoChain );
231
232 // Properly classified chain must match both wildcard and exact patterns.
233 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 1.0 ), false, &trackClassified,
234 &trackClassified );
235 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 1.0 ), false, &trackClassified,
236 &trackClassified );
237 testEvalExpr( wxT( "A.inNetChainClass('High*')" ), VAL( 1.0 ), false, &trackClassified,
238 &trackClassified );
239 testEvalExpr( wxT( "A.inNetChainClass('LowSpeed')" ), VAL( 0.0 ), false, &trackClassified,
240 &trackClassified );
241}
242
243BOOST_AUTO_TEST_CASE( ParentNavigation )
244{
246 propMgr.Rebuild();
247
248 BOARD brd;
249
250 FOOTPRINT fp( &brd );
251 fp.SetReference( wxT( "J1" ) );
252
253 // A text item living inside the footprint. Its direct parent is the footprint, so
254 // "A.Parent" navigates to J1.
255 PCB_TEXT* text = new PCB_TEXT( &fp );
256 text->SetText( wxT( "J1" ) );
257 fp.Add( text );
258
259 // The headline capability: getField() only returns a value when its receiver is a
260 // footprint, so this passes solely because navigation steps from the text to its parent.
261 testEvalExpr( wxT( "A.Parent.getField('Reference') == 'J1'" ), VAL( 1.0 ), false, text, text );
262
263 // The exact pattern from the issue (parent field compared to the text's own value).
264 testEvalExpr( wxT( "A.Parent.getField('Reference') == A.Text" ), VAL( 1.0 ), false, text, text );
265
266 // The parent resolves to a footprint object that can be type-queried.
267 testEvalExpr( wxT( "A.Parent.Type == 'Footprint'" ), VAL( 1.0 ), false, text, text );
268
269 // Regression: the terminal "Parent" string still yields the parent footprint reference,
270 // i.e. the object and the string refer to the same parent.
271 testEvalExpr( wxT( "A.Parent == 'J1'" ), VAL( 1.0 ), false, text, text );
272
273 // Without navigation getField() has a non-footprint receiver and returns "", so this must
274 // NOT match - it guards against the navigation silently applying to the base item.
275 testEvalExpr( wxT( "A.getField('Reference') == 'J1'" ), VAL( 0.0 ), false, text, text );
276
277 // Error cases. These are code-generation errors (not parse errors), which the shared
278 // testEvalExpr does not observe through Compile()'s return value, so check the pending-error
279 // status directly. None of these expressions contain a bare number, so the only error that
280 // can be raised is the unrecognized item/property we are testing for.
281 auto expectCompileError =
282 []( const wxString& aExpr )
283 {
284 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
285 PCBEXPR_UCODE ucode;
287
288 compiler.Compile( aExpr, &ucode, &preflight );
289
291 "Expected a compile error for: " << aExpr.mb_str() );
292 };
293
294 // An unknown property on the parent, an unknown navigation step, and navigation on the
295 // layer pseudo-item (which has no parent).
296 expectCompileError( wxT( "A.Parent.bogusProperty" ) );
297 expectCompileError( wxT( "A.bogus.Reference == 'J1'" ) );
298 expectCompileError( wxT( "L.Parent.Type == 'Footprint'" ) );
299}
300
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
std::shared_ptr< NET_SETTINGS > m_NetSettings
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1086
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1149
void SetReference(const wxString &aReference)
Definition footprint.h:847
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
bool IsErrorPending() const
const ERROR_STATUS & GetError() const
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
VALUE * Run(CONTEXT *ctx)
virtual const wxString & AsString() const
virtual double AsDouble() const
VAR_TYPE_T GetType() const
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:38
Handle the data for a net.
Definition netinfo.h:46
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:221
void SetItems(BOARD_ITEM *a, BOARD_ITEM *b=nullptr)
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
Provide class metadata.Helper macro to map type hashes to names.
static PROPERTY_MANAGER & Instance()
void Rebuild()
Rebuild the list of all registered properties.
@ NULL_CONSTRAINT
Definition drc_rule.h:50
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
friend std::ostream & operator<<(std::ostream &os, const EXPR_TO_TEST &expr)
LIBEVAL::VALUE expectedResult
BOOST_DATA_TEST_CASE(ConvertToKicadUnit, boost::unit_test::data::make(altium_to_kicad_unit), input_value, expected_result)
Test conversation from Altium internal units into KiCad internal units.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
static const std::vector< EXPR_TO_TEST > simpleExpressions
static bool testEvalExpr(const wxString &expr, const LIBEVAL::VALUE &expectedResult, bool expectError=false, BOARD_ITEM *itemA=nullptr, BOARD_ITEM *itemB=nullptr)
LIBEVAL::VALUE VAL
static const std::vector< EXPR_TO_TEST > introspectionExpressions
BOOST_AUTO_TEST_CASE(IntrospectedProperties)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")