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 bool matches = m_regex_search.Matches( aPattern );
249
250 if( !matches || m_regex_search.GetMatchCount() < 5 )
251 return false;
252
253 m_pattern = aPattern;
254 wxString key = m_regex_search.GetMatch( aPattern, 1 );
255 wxString rel = m_regex_search.GetMatch( aPattern, 2 );
256 wxString val = m_regex_search.GetMatch( aPattern, 3 );
257 wxString unit = m_regex_search.GetMatch( aPattern, 4 );
258
259 m_key = key.Lower();
260
261 if( rel == wxS( "<" ) )
262 m_relation = LT;
263 else if( rel == wxS( "<=" ) )
264 m_relation = LE;
265 else if( rel == wxS( "=" ) )
266 m_relation = EQ;
267 else if( rel == wxS( ">=" ) )
268 m_relation = GE;
269 else if( rel == wxS( ">" ) )
270 m_relation = GT;
271 else
272 return false;
273
274 if( val == "" )
275 {
276 // Matching on empty values keeps the match list from going empty when the user
277 // types the relational operator character, which helps prevent confusion.
278 m_relation = ANY;
279 }
280 else if( !val.ToCDouble( &m_value ) )
281 {
282 return false;
283 }
284
285 auto unit_it = m_units.find( unit.Lower() );
286
287 if( unit_it != m_units.end() )
288 m_value *= unit_it->second;
289 else
290 return false;
291
292 m_pattern = aPattern;
293
294 return true;
295}
296
297
299{
300 return m_pattern;
301}
302
303
305{
306 wxStringTokenizer tokenizer( aCandidate );
307 size_t lastpos = 0;
308
309 while( tokenizer.HasMoreTokens() )
310 {
311 const wxString token = tokenizer.GetNextToken();
312 int found_delta = FindOne( token );
313
314 if( found_delta != EDA_PATTERN_NOT_FOUND )
315 {
316 size_t found = (size_t) found_delta + lastpos;
317 return { static_cast<int>( CLAMPED_VAL_INT_MAX( found ) ), 0 };
318 }
319
320 lastpos = tokenizer.GetPosition();
321 }
322
323 return {};
324}
325
326
327int EDA_PATTERN_MATCH_RELATIONAL::FindOne( const wxString& aCandidate ) const
328{
329 bool matches = m_regex_description.Matches( aCandidate );
330
331 if( !matches )
333
334 size_t start, len;
335 m_regex_description.GetMatch( &start, &len, 0 );
336 wxString key = m_regex_description.GetMatch( aCandidate, 1 );
337 wxString val = m_regex_description.GetMatch( aCandidate, 2 );
338 wxString unit = m_regex_description.GetMatch( aCandidate, 3 );
339
340 int istart = static_cast<int>( CLAMPED_VAL_INT_MAX( start ) );
341
342 if( key.Lower() != m_key )
344
345 double val_parsed;
346
347 if( !val.ToCDouble( &val_parsed ) )
349
350 auto unit_it = m_units.find( unit.Lower() );
351
352 if( unit_it != m_units.end() )
353 val_parsed *= unit_it->second;
354
355 switch( m_relation )
356 {
357 case LT: return val_parsed < m_value ? istart : EDA_PATTERN_NOT_FOUND;
358 case LE: return val_parsed <= m_value ? istart : EDA_PATTERN_NOT_FOUND;
359 case EQ: return val_parsed == m_value ? istart : EDA_PATTERN_NOT_FOUND;
360 case GE: return val_parsed >= m_value ? istart : EDA_PATTERN_NOT_FOUND;
361 case GT: return val_parsed > m_value ? istart : EDA_PATTERN_NOT_FOUND;
362 case ANY: return istart;
363 default: return EDA_PATTERN_NOT_FOUND;
364 }
365}
366
367
369 R"((\w+)[=:]([-+]?[\d.]+)(\w*))", wxRE_ADVANCED );
371 R"(^(\w+)(<|<=|=|>=|>)([-+]?[\d.]*)(\w*)$)", wxRE_ADVANCED );
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_NETCLASS:
405 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX_ANCHORED>() );
406 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD_ANCHORED>() );
407 break;
408
409 case CTX_SIGNAL:
410 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
411 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
412 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
413 break;
414
415 case CTX_SEARCH:
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}
422
423
424bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition )
425{
426 aPosition = EDA_PATTERN_NOT_FOUND;
427 aMatchersTriggered = 0;
428
429 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
430 {
431 EDA_PATTERN_MATCH::FIND_RESULT local_find = matcher->Find( aTerm );
432
433 if( local_find )
434 {
435 aMatchersTriggered += 1;
436
437 if( local_find.start < aPosition || aPosition == EDA_PATTERN_NOT_FOUND )
438 aPosition = local_find.start;
439 }
440 }
441
442 return aPosition != EDA_PATTERN_NOT_FOUND;
443}
444
445
446bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm )
447{
448 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
449 {
450 if( matcher->Find( aTerm ).start >= 0 )
451 return true;
452 }
453
454 return false;
455}
456
457
458bool EDA_COMBINED_MATCHER::StartsWith( const wxString& aTerm )
459{
460 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
461 {
462 if( matcher->Find( aTerm ).start == 0 )
463 return true;
464 }
465
466 return false;
467}
468
469
470int EDA_COMBINED_MATCHER::ScoreTerms( std::vector<SEARCH_TERM>& aWeightedTerms )
471{
472 int score = 0;
473
474 for( SEARCH_TERM& term : aWeightedTerms )
475 {
476 if( !term.Normalized )
477 {
478 term.Text = term.Text.MakeLower().Trim( false ).Trim( true );
479 term.Normalized = true;
480 }
481
482 int found_pos = EDA_PATTERN_NOT_FOUND;
483 int matchers_fired = 0;
484
485 if( GetPattern() == term.Text )
486 {
487 score += 8 * term.Score;
488 }
489 else if( Find( term.Text, matchers_fired, found_pos ) )
490 {
491 if( found_pos == 0 )
492 score += 2 * term.Score;
493 else
494 score += term.Score;
495 }
496 }
497
498 return score;
499}
500
501
502wxString const& EDA_COMBINED_MATCHER::GetPattern() const
503{
504 return m_pattern;
505}
506
507
508void EDA_COMBINED_MATCHER::AddMatcher( const wxString &aPattern,
509 std::unique_ptr<EDA_PATTERN_MATCH> aMatcher )
510{
511 if ( aMatcher->SetPattern( aPattern ) )
512 m_matchers.push_back( std::move( aMatcher ) );
513}
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_SIGNAL
@ CTX_NETCLASS
@ CTX_SEARCH
@ CTX_LIBITEM