KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eda_pattern_match.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 (C) 2015-2017 Chris Pavlina <[email protected]>
5 * Copyright (C) 2015-2023 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <eda_pattern_match.h>
26#include <limits>
27#include <wx/log.h>
28#include <wx/tokenzr.h>
29#include <algorithm>
30
31// Helper to make the code cleaner when we want this operation
32#define CLAMPED_VAL_INT_MAX( x ) std::min( x, static_cast<size_t>( std::numeric_limits<int>::max() ) )
33
34bool EDA_PATTERN_MATCH_SUBSTR::SetPattern( const wxString& aPattern )
35{
36 m_pattern = aPattern;
37 return true;
38}
39
40
42{
43 return m_pattern;
44}
45
46
48{
49 int loc = aCandidate.Find( m_pattern );
50
51 if( loc == wxNOT_FOUND )
52 return {};
53 else
54 return { loc, static_cast<int>( m_pattern.size() ) };
55}
56
57
62{
63 wxLogLevel m_old_level;
64
65public:
66 WX_LOGLEVEL_CONTEXT( wxLogLevel level )
67 {
68 m_old_level = wxLog::GetLogLevel();
69 wxLog::SetLogLevel( level );
70 }
71
73 {
74 wxLog::SetLogLevel( m_old_level );
75 }
76};
77
78
79bool EDA_PATTERN_MATCH_REGEX::SetPattern( const wxString& aPattern )
80{
81 if( aPattern.StartsWith( "^" ) && aPattern.EndsWith( "$" ) )
82 {
83 m_pattern = aPattern;
84 }
85 else if( aPattern.StartsWith( "/" ) )
86 {
87 // Requiring a '/' on the end means they get no feedback while they type
88 m_pattern = aPattern.Mid( 1 );
89
90 if( m_pattern.EndsWith( "/" ) )
91 m_pattern = m_pattern.Left( m_pattern.length() - 1 );
92 }
93 else
94 {
95 // For now regular expressions must be explicit
96 return false;
97 }
98
99 // Evil and undocumented: wxRegEx::Compile calls wxLogError on error, even
100 // though it promises to just return false. Silence the error.
101 WX_LOGLEVEL_CONTEXT ctx( wxLOG_FatalError );
102
103 return m_regex.Compile( m_pattern, wxRE_ADVANCED );
104}
105
106
107bool EDA_PATTERN_MATCH_REGEX_ANCHORED::SetPattern( const wxString& aPattern )
108{
109 wxString pattern( aPattern );
110
111 if( !pattern.StartsWith( wxT( "^" ) ) )
112 pattern = wxT( "^" ) + pattern;
113
114 if( !pattern.EndsWith( wxT( "$" ) ) )
115 pattern += wxT( "$" );
116
117 return EDA_PATTERN_MATCH_REGEX::SetPattern( pattern );
118}
119
120
122{
123 return m_pattern;
124}
125
126
128{
129 if( m_regex.IsValid() )
130 {
131 if( m_regex.Matches( aCandidate ) )
132 {
133 size_t start, len;
134 m_regex.GetMatch( &start, &len, 0 );
135
136 return { static_cast<int>( CLAMPED_VAL_INT_MAX( start ) ),
137 static_cast<int>( CLAMPED_VAL_INT_MAX( len ) ) };
138 }
139 else
140 {
141 return {};
142 }
143 }
144 else
145 {
146 int loc = aCandidate.Find( m_pattern );
147
148 if( loc == wxNOT_FOUND )
149 return {};
150 else
151 return { loc, static_cast<int>( m_pattern.size() ) };
152 }
153}
154
155
156bool EDA_PATTERN_MATCH_WILDCARD::SetPattern( const wxString& aPattern )
157{
158 m_wildcard_pattern = aPattern;
159
160 // Compile the wildcard string to a regular expression
161 wxString regex;
162 regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
163
164 const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
165
166 for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
167 {
168 wxUniChar c = *it;
169
170 if( c == '?' )
171 {
172 regex += wxT( "." );
173 }
174 else if( c == '*' )
175 {
176 regex += wxT( ".*" );
177 }
178 else if( to_replace.Find( c ) != wxNOT_FOUND )
179 {
180 regex += "\\";
181 regex += c;
182 }
183 else
184 {
185 regex += c;
186 }
187 }
188
189 return EDA_PATTERN_MATCH_REGEX::SetPattern( wxS( "/" ) + regex + wxS( "/" ) );
190}
191
192
194{
195 return m_wildcard_pattern;
196}
197
198
200{
201 return EDA_PATTERN_MATCH_REGEX::Find( aCandidate );
202}
203
204
206{
207 m_wildcard_pattern = aPattern;
208
209 // Compile the wildcard string to a regular expression
210 wxString regex;
211 regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
212
213 const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
214
215 regex += wxT( "^" );
216
217 for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
218 {
219 wxUniChar c = *it;
220
221 if( c == '?' )
222 {
223 regex += wxT( "." );
224 }
225 else if( c == '*' )
226 {
227 regex += wxT( ".*" );
228 }
229 else if( to_replace.Find( c ) != wxNOT_FOUND )
230 {
231 regex += wxS( "\\" );
232 regex += c;
233 }
234 else
235 {
236 regex += c;
237 }
238 }
239
240 regex += wxT( "$" );
241
243}
244
245
246bool EDA_PATTERN_MATCH_RELATIONAL::SetPattern( const wxString& aPattern )
247{
248 wxRegEx regex_search( R"(^(\w+)(<|<=|=|>=|>)([-+]?[\d.]*)(\w*)$)", wxRE_ADVANCED );
249
250 bool matches = regex_search.IsValid() && regex_search.Matches( aPattern );
251
252 if( !matches || regex_search.GetMatchCount() < 5 )
253 return false;
254
255 m_pattern = aPattern;
256 wxString key = regex_search.GetMatch( aPattern, 1 );
257 wxString rel = regex_search.GetMatch( aPattern, 2 );
258 wxString val = regex_search.GetMatch( aPattern, 3 );
259 wxString unit = regex_search.GetMatch( aPattern, 4 );
260
261 m_key = key.Lower();
262
263 if( rel == wxS( "<" ) )
264 m_relation = LT;
265 else if( rel == wxS( "<=" ) )
266 m_relation = LE;
267 else if( rel == wxS( "=" ) )
268 m_relation = EQ;
269 else if( rel == wxS( ">=" ) )
270 m_relation = GE;
271 else if( rel == wxS( ">" ) )
272 m_relation = GT;
273 else
274 return false;
275
276 if( val == "" )
277 {
278 // Matching on empty values keeps the match list from going empty when the user
279 // types the relational operator character, which helps prevent confusion.
280 m_relation = ANY;
281 }
282 else if( !val.ToCDouble( &m_value ) )
283 {
284 return false;
285 }
286
287 auto unit_it = m_units.find( unit.Lower() );
288
289 if( unit_it != m_units.end() )
290 m_value *= unit_it->second;
291 else
292 return false;
293
294 m_pattern = aPattern;
295
296 return true;
297}
298
299
301{
302 return m_pattern;
303}
304
305
307{
308 wxStringTokenizer tokenizer( aCandidate );
309 size_t lastpos = 0;
310
311 while( tokenizer.HasMoreTokens() )
312 {
313 const wxString token = tokenizer.GetNextToken();
314 int found_delta = FindOne( token );
315
316 if( found_delta != EDA_PATTERN_NOT_FOUND )
317 {
318 size_t found = (size_t) found_delta + lastpos;
319 return { static_cast<int>( CLAMPED_VAL_INT_MAX( found ) ), 0 };
320 }
321
322 lastpos = tokenizer.GetPosition();
323 }
324
325 return {};
326}
327
328
329int EDA_PATTERN_MATCH_RELATIONAL::FindOne( const wxString& aCandidate ) const
330{
331 wxRegEx regex_description( R"((\w+)[=:]([-+]?[\d.]+)(\w*))", wxRE_ADVANCED );
332
333 bool matches = regex_description.IsValid() && regex_description.Matches( aCandidate );
334
335 if( !matches )
337
338 size_t start, len;
339 regex_description.GetMatch( &start, &len, 0 );
340 wxString key = regex_description.GetMatch( aCandidate, 1 );
341 wxString val = regex_description.GetMatch( aCandidate, 2 );
342 wxString unit = regex_description.GetMatch( aCandidate, 3 );
343
344 int istart = static_cast<int>( CLAMPED_VAL_INT_MAX( start ) );
345
346 if( key.Lower() != m_key )
348
349 double val_parsed;
350
351 if( !val.ToCDouble( &val_parsed ) )
353
354 auto unit_it = m_units.find( unit.Lower() );
355
356 if( unit_it != m_units.end() )
357 val_parsed *= unit_it->second;
358
359 switch( m_relation )
360 {
361 case LT: return val_parsed < m_value ? istart : EDA_PATTERN_NOT_FOUND;
362 case LE: return val_parsed <= m_value ? istart : EDA_PATTERN_NOT_FOUND;
363 case EQ: return val_parsed == m_value ? istart : EDA_PATTERN_NOT_FOUND;
364 case GE: return val_parsed >= m_value ? istart : EDA_PATTERN_NOT_FOUND;
365 case GT: return val_parsed > m_value ? istart : EDA_PATTERN_NOT_FOUND;
366 case ANY: return istart;
367 default: return EDA_PATTERN_NOT_FOUND;
368 }
369}
370
371
372const std::map<wxString, double> EDA_PATTERN_MATCH_RELATIONAL::m_units = {
373 { wxS( "p" ), 1e-12 },
374 { wxS( "n" ), 1e-9 },
375 { wxS( "u" ), 1e-6 },
376 { wxS( "m" ), 1e-3 },
377 { wxS( "" ), 1. },
378 { wxS( "k" ), 1e3 },
379 { wxS( "meg" ), 1e6 },
380 { wxS( "g" ), 1e9 },
381 { wxS( "t" ), 1e12 },
382 { wxS( "ki" ), 1024. },
383 { wxS( "mi" ), 1048576. },
384 { wxS( "gi" ), 1073741824. },
385 { wxS( "ti" ), 1099511627776. } };
386
387
389 COMBINED_MATCHER_CONTEXT aContext ) :
390 m_pattern( aPattern )
391{
392 switch( aContext )
393 {
394 case CTX_LIBITEM:
395 // Whatever syntax users prefer, it shall be matched.
396 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
397 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
398 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_RELATIONAL>() );
399 // If any of the above matchers couldn't be created because the pattern
400 // syntax does not match, the substring will try its best.
401 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
402 break;
403
404 case CTX_NET:
405 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
406 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
407 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
408 break;
409
410 case CTX_NETCLASS:
411 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX_ANCHORED>() );
412 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD_ANCHORED>() );
413 break;
414
415 case CTX_SIGNAL:
416 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
417 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
418 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
419 break;
420
421 case CTX_SEARCH:
422 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
423 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
424 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
425 break;
426 }
427}
428
429
430bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition )
431{
432 aPosition = EDA_PATTERN_NOT_FOUND;
433 aMatchersTriggered = 0;
434
435 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
436 {
437 EDA_PATTERN_MATCH::FIND_RESULT local_find = matcher->Find( aTerm );
438
439 if( local_find )
440 {
441 aMatchersTriggered += 1;
442
443 if( local_find.start < aPosition || aPosition == EDA_PATTERN_NOT_FOUND )
444 aPosition = local_find.start;
445 }
446 }
447
448 return aPosition != EDA_PATTERN_NOT_FOUND;
449}
450
451
452bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm )
453{
454 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
455 {
456 if( matcher->Find( aTerm ).start >= 0 )
457 return true;
458 }
459
460 return false;
461}
462
463
464bool EDA_COMBINED_MATCHER::StartsWith( const wxString& aTerm )
465{
466 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
467 {
468 if( matcher->Find( aTerm ).start == 0 )
469 return true;
470 }
471
472 return false;
473}
474
475
476int EDA_COMBINED_MATCHER::ScoreTerms( std::vector<SEARCH_TERM>& aWeightedTerms )
477{
478 int score = 0;
479
480 for( SEARCH_TERM& term : aWeightedTerms )
481 {
482 if( !term.Normalized )
483 {
484 term.Text = term.Text.MakeLower().Trim( false ).Trim( true );
485 term.Normalized = true;
486 }
487
488 int found_pos = EDA_PATTERN_NOT_FOUND;
489 int matchers_fired = 0;
490
491 if( GetPattern() == term.Text )
492 {
493 score += 8 * term.Score;
494 }
495 else if( Find( term.Text, matchers_fired, found_pos ) )
496 {
497 if( found_pos == 0 )
498 score += 2 * term.Score;
499 else
500 score += term.Score;
501 }
502 }
503
504 return score;
505}
506
507
508wxString const& EDA_COMBINED_MATCHER::GetPattern() const
509{
510 return m_pattern;
511}
512
513
514void EDA_COMBINED_MATCHER::AddMatcher( const wxString &aPattern,
515 std::unique_ptr<EDA_PATTERN_MATCH> aMatcher )
516{
517 if ( aMatcher->SetPattern( aPattern ) )
518 m_matchers.push_back( std::move( aMatcher ) );
519}
int ScoreTerms(std::vector< SEARCH_TERM > &aWeightedTerms)
std::vector< std::unique_ptr< EDA_PATTERN_MATCH > > m_matchers
void AddMatcher(const wxString &aPattern, std::unique_ptr< EDA_PATTERN_MATCH > aMatcher)
EDA_COMBINED_MATCHER(const wxString &aPattern, COMBINED_MATCHER_CONTEXT aContext)
bool Find(const wxString &aTerm, int &aMatchersTriggered, int &aPosition)
const wxString & GetPattern() const
bool StartsWith(const wxString &aTerm)
virtual bool SetPattern(const wxString &aPattern) override
Set the pattern against which candidates will be matched.
virtual wxString const & GetPattern() const override
Return the pattern passed to SetPattern().
virtual FIND_RESULT Find(const wxString &aCandidate) const override
Return the location and possibly length of a match iff a given candidate string matches the set patte...
virtual bool SetPattern(const wxString &aPattern) override
Set the pattern against which candidates will be matched.
virtual bool SetPattern(const wxString &aPattern) override
Set the pattern against which candidates will be matched.
int FindOne(const wxString &aCandidate) const
virtual FIND_RESULT Find(const wxString &aCandidate) const override
Return the location and possibly length of a match iff a given candidate string matches the set patte...
virtual wxString const & GetPattern() const override
Return the pattern passed to SetPattern().
static const std::map< wxString, double > m_units
virtual wxString const & GetPattern() const override
Return the pattern passed to SetPattern().
virtual bool SetPattern(const wxString &aPattern) override
Set the pattern against which candidates will be matched.
virtual FIND_RESULT Find(const wxString &aCandidate) const override
Return the location and possibly length of a match iff a given candidate string matches the set patte...
virtual bool SetPattern(const wxString &aPattern) override
Set the pattern against which candidates will be matched.
virtual FIND_RESULT Find(const wxString &aCandidate) const override
Return the location and possibly length of a match iff a given candidate string matches the set patte...
virtual bool SetPattern(const wxString &aPattern) override
Set the pattern against which candidates will be matched.
virtual wxString const & GetPattern() const override
Return the pattern passed to SetPattern().
Context class to set wx loglevel for a block, and always restore it at the end.
WX_LOGLEVEL_CONTEXT(wxLogLevel level)
#define CLAMPED_VAL_INT_MAX(x)
Abstract pattern-matching tool and implementations.
static const int EDA_PATTERN_NOT_FOUND
COMBINED_MATCHER_CONTEXT
@ CTX_NET
@ CTX_SIGNAL
@ CTX_NETCLASS
@ CTX_SEARCH
@ CTX_LIBITEM