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 The 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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <eda_pattern_match.h>
22#include <limits>
23#include <wx/log.h>
24#include <wx/tokenzr.h>
25#include <algorithm>
26
27// Helper to make the code cleaner when we want this operation
28#define CLAMPED_VAL_INT_MAX( x ) \
29 std::min( x, static_cast<size_t>( std::numeric_limits<int>::max() ) )
30
31
32bool EDA_PATTERN_MATCH_SUBSTR::SetPattern( const wxString& aPattern )
33{
34 m_pattern = aPattern;
35 return true;
36}
37
38
40{
41 return m_pattern;
42}
43
44
46{
47 int loc = aCandidate.Find( m_pattern );
48
49 if( loc == wxNOT_FOUND )
50 return {};
51 else
52 return { loc, static_cast<int>( m_pattern.size() ) };
53}
54
55
60{
61 wxLogLevel m_old_level;
62
63public:
64 WX_LOGLEVEL_CONTEXT( wxLogLevel level )
65 {
66 m_old_level = wxLog::GetLogLevel();
67 wxLog::SetLogLevel( level );
68 }
69
71 {
72 wxLog::SetLogLevel( m_old_level );
73 }
74};
75
76
77bool EDA_PATTERN_MATCH_REGEX::SetPattern( const wxString& aPattern )
78{
79 if( aPattern.StartsWith( "^" ) && aPattern.EndsWith( "$" ) )
80 {
81 m_pattern = aPattern;
82 }
83 else if( aPattern.StartsWith( "/" ) )
84 {
85 // Requiring a '/' on the end means they get no feedback while they type
86 m_pattern = aPattern.Mid( 1 );
87
88 if( m_pattern.EndsWith( "/" ) )
89 m_pattern = m_pattern.Left( m_pattern.length() - 1 );
90 }
91 else
92 {
93 // For now regular expressions must be explicit
94 return false;
95 }
96
97 // Evil and undocumented: wxRegEx::Compile calls wxLogError on error, even
98 // though it promises to just return false. Silence the error.
99 WX_LOGLEVEL_CONTEXT ctx( wxLOG_FatalError );
100
101 return m_regex.Compile( m_pattern, wxRE_ADVANCED );
102}
103
104
105bool EDA_PATTERN_MATCH_REGEX_ANCHORED::SetPattern( const wxString& aPattern )
106{
107 wxString pattern( aPattern );
108
109 if( !pattern.StartsWith( wxT( "^" ) ) )
110 pattern = wxT( "^" ) + pattern;
111
112 if( !pattern.EndsWith( wxT( "$" ) ) )
113 pattern += wxT( "$" );
114
115 return EDA_PATTERN_MATCH_REGEX::SetPattern( pattern );
116}
117
118
120{
121 return m_pattern;
122}
123
124
126{
127 if( m_regex.IsValid() )
128 {
129 if( m_regex.Matches( aCandidate ) )
130 {
131 size_t start, len;
132 m_regex.GetMatch( &start, &len, 0 );
133
134 return { static_cast<int>( CLAMPED_VAL_INT_MAX( start ) ),
135 static_cast<int>( CLAMPED_VAL_INT_MAX( len ) ) };
136 }
137 else
138 {
139 return {};
140 }
141 }
142 else
143 {
144 int loc = aCandidate.Find( m_pattern );
145
146 if( loc == wxNOT_FOUND )
147 return {};
148 else
149 return { loc, static_cast<int>( m_pattern.size() ) };
150 }
151}
152
153
154bool EDA_PATTERN_MATCH_WILDCARD::SetPattern( const wxString& aPattern )
155{
156 m_wildcard_pattern = aPattern;
157
158 // Compile the wildcard string to a regular expression
159 wxString regex;
160 regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
161
162 const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
163
164 for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
165 {
166 wxUniChar c = *it;
167
168 if( c == '?' )
169 {
170 regex += wxT( "." );
171 }
172 else if( c == '*' )
173 {
174 regex += wxT( ".*" );
175 }
176 else if( to_replace.Find( c ) != wxNOT_FOUND )
177 {
178 regex += "\\";
179 regex += c;
180 }
181 else
182 {
183 regex += c;
184 }
185 }
186
187 return EDA_PATTERN_MATCH_REGEX::SetPattern( wxS( "/" ) + regex + wxS( "/" ) );
188}
189
190
192{
193 return m_wildcard_pattern;
194}
195
196
198{
199 return EDA_PATTERN_MATCH_REGEX::Find( aCandidate );
200}
201
202
204{
205 m_wildcard_pattern = aPattern;
206
207 // Compile the wildcard string to a regular expression
208 wxString regex;
209 regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
210
211 const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
212
213 regex += wxT( "^" );
214
215 for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
216 {
217 wxUniChar c = *it;
218
219 if( c == '?' )
220 {
221 regex += wxT( "." );
222 }
223 else if( c == '*' )
224 {
225 regex += wxT( ".*" );
226 }
227 else if( to_replace.Find( c ) != wxNOT_FOUND )
228 {
229 regex += wxS( "\\" );
230 regex += c;
231 }
232 else
233 {
234 regex += c;
235 }
236 }
237
238 regex += wxT( "$" );
239
241}
242
243
244bool EDA_PATTERN_MATCH_RELATIONAL::SetPattern( const wxString& aPattern )
245{
246 wxRegEx regex_search( R"(^(\w+)(<|<=|=|>=|>)([-+]?[\d.]*)(\w*)$)", wxRE_ADVANCED );
247
248 bool matches = regex_search.IsValid() && regex_search.Matches( aPattern );
249
250 if( !matches || regex_search.GetMatchCount() < 5 )
251 return false;
252
253 m_pattern = aPattern;
254 wxString key = regex_search.GetMatch( aPattern, 1 );
255 wxString rel = regex_search.GetMatch( aPattern, 2 );
256 wxString val = regex_search.GetMatch( aPattern, 3 );
257 wxString unit = 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, " \t\r\n", wxTOKEN_STRTOK );
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 wxRegEx regex_description( R"((\w+)[=:]([-+]?[\d.]+)(\w*))", wxRE_ADVANCED );
330
331 bool matches = regex_description.IsValid() && regex_description.Matches( aCandidate );
332
333 if( !matches )
335
336 size_t start, len;
337 regex_description.GetMatch( &start, &len, 0 );
338 wxString key = regex_description.GetMatch( aCandidate, 1 );
339 wxString val = regex_description.GetMatch( aCandidate, 2 );
340 wxString unit = regex_description.GetMatch( aCandidate, 3 );
341
342 int istart = static_cast<int>( CLAMPED_VAL_INT_MAX( start ) );
343
344 if( key.Lower() != m_key )
346
347 double val_parsed;
348
349 if( !val.ToCDouble( &val_parsed ) )
351
352 auto unit_it = m_units.find( unit.Lower() );
353
354 if( unit_it != m_units.end() )
355 val_parsed *= unit_it->second;
356
357 switch( m_relation )
358 {
359 case LT: return val_parsed < m_value ? istart : EDA_PATTERN_NOT_FOUND;
360 case LE: return val_parsed <= m_value ? istart : EDA_PATTERN_NOT_FOUND;
361 case EQ: return val_parsed == m_value ? istart : EDA_PATTERN_NOT_FOUND;
362 case GE: return val_parsed >= m_value ? istart : EDA_PATTERN_NOT_FOUND;
363 case GT: return val_parsed > m_value ? istart : EDA_PATTERN_NOT_FOUND;
364 case ANY: return istart;
365 default: return EDA_PATTERN_NOT_FOUND;
366 }
367}
368
369
370const std::map<wxString, double> EDA_PATTERN_MATCH_RELATIONAL::m_units = {
371 { wxS( "p" ), 1e-12 },
372 { wxS( "n" ), 1e-9 },
373 { wxS( "u" ), 1e-6 },
374 { wxS( "m" ), 1e-3 },
375 { wxS( "" ), 1. },
376 { wxS( "k" ), 1e3 },
377 { wxS( "meg" ), 1e6 },
378 { wxS( "g" ), 1e9 },
379 { wxS( "t" ), 1e12 },
380 { wxS( "ki" ), 1024. },
381 { wxS( "mi" ), 1048576. },
382 { wxS( "gi" ), 1073741824. },
383 { wxS( "ti" ), 1099511627776. } };
384
385
387 COMBINED_MATCHER_CONTEXT aContext ) :
388 m_pattern( aPattern )
389{
390 switch( aContext )
391 {
392 case CTX_LIBITEM:
393 // Whatever syntax users prefer, it shall be matched.
394 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
395 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
396 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_RELATIONAL>() );
397
398 // If any of the above matchers couldn't be created because the pattern
399 // syntax does not match, the substring will try its best.
400 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
401 break;
402
403 case CTX_NET:
404 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
405 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
406 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
407 break;
408
409 case CTX_NETCLASS:
410 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX_ANCHORED>() );
411 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD_ANCHORED>() );
412 break;
413
414 case CTX_SIGNAL:
415 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
416 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
417 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
418 break;
419
420 case CTX_SEARCH:
421 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
422 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
423 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
424 break;
425 }
426}
427
428
429bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition )
430{
431 aPosition = EDA_PATTERN_NOT_FOUND;
432 aMatchersTriggered = 0;
433
434 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
435 {
436 EDA_PATTERN_MATCH::FIND_RESULT local_find = matcher->Find( aTerm );
437
438 if( local_find )
439 {
440 aMatchersTriggered += 1;
441
442 if( local_find.start < aPosition || aPosition == EDA_PATTERN_NOT_FOUND )
443 aPosition = local_find.start;
444 }
445 }
446
447 return aPosition != EDA_PATTERN_NOT_FOUND;
448}
449
450
451bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm )
452{
453 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
454 {
455 if( matcher->Find( aTerm ).start >= 0 )
456 return true;
457 }
458
459 return false;
460}
461
462
463bool EDA_COMBINED_MATCHER::StartsWith( const wxString& aTerm )
464{
465 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
466 {
467 if( matcher->Find( aTerm ).start == 0 )
468 return true;
469 }
470
471 return false;
472}
473
474
475int EDA_COMBINED_MATCHER::ScoreTerms( std::vector<SEARCH_TERM>& aWeightedTerms, bool* aExactMatch )
476{
477 int score = 0;
478
479 for( SEARCH_TERM& term : aWeightedTerms )
480 {
481 if( !term.Normalized )
482 {
483 term.Text = term.Text.MakeLower().Trim( false ).Trim( true );
484
485 // Don't cause KiCad to hang if someone accidentally pastes the PCB or schematic
486 // into the search box.
487 if( term.Text.Length() > 1000 )
488 term.Text = term.Text.Left( 1000 );
489
490 term.Normalized = true;
491 }
492
493 int found_pos = EDA_PATTERN_NOT_FOUND;
494 int matchers_fired = 0;
495
496 if( GetPattern() == term.Text )
497 {
498 score += 8 * term.Score;
499
500 if( aExactMatch )
501 *aExactMatch = true;
502 }
503 else if( Find( term.Text, matchers_fired, found_pos ) )
504 {
505 if( found_pos == 0 )
506 score += 2 * term.Score;
507 else
508 score += term.Score;
509 }
510 }
511
512 return score;
513}
514
515
516wxString const& EDA_COMBINED_MATCHER::GetPattern() const
517{
518 return m_pattern;
519}
520
521
522void EDA_COMBINED_MATCHER::AddMatcher( const wxString &aPattern,
523 std::unique_ptr<EDA_PATTERN_MATCH> aMatcher )
524{
525 if ( aMatcher->SetPattern( aPattern ) )
526 m_matchers.push_back( std::move( aMatcher ) );
527}
int ScoreTerms(std::vector< SEARCH_TERM > &aWeightedTerms, bool *aExactMatch=nullptr)
std::vector< std::unique_ptr< EDA_PATTERN_MATCH > > m_matchers
void AddMatcher(const wxString &aPattern, std::unique_ptr< EDA_PATTERN_MATCH > aMatcher)
Add matcher if it can compile the pattern.
EDA_COMBINED_MATCHER(const wxString &aPattern, COMBINED_MATCHER_CONTEXT aContext)
bool Find(const wxString &aTerm, int &aMatchersTriggered, int &aPosition)
Look in all existing matchers, return the earliest match of any of the existing.
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 if a given candidate string matches the set patter...
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 if a given candidate string matches the set patter...
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 if a given candidate string matches the set patter...
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 if a given candidate string matches the set patter...
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
A structure for storing weighted search terms.