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, 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 ) \
33 std::min( x, static_cast<size_t>( std::numeric_limits<int>::max() ) )
34
35
36bool EDA_PATTERN_MATCH_SUBSTR::SetPattern( const wxString& aPattern )
37{
38 m_pattern = aPattern;
39 return true;
40}
41
42
44{
45 return m_pattern;
46}
47
48
50{
51 int loc = aCandidate.Find( m_pattern );
52
53 if( loc == wxNOT_FOUND )
54 return {};
55 else
56 return { loc, static_cast<int>( m_pattern.size() ) };
57}
58
59
64{
65 wxLogLevel m_old_level;
66
67public:
68 WX_LOGLEVEL_CONTEXT( wxLogLevel level )
69 {
70 m_old_level = wxLog::GetLogLevel();
71 wxLog::SetLogLevel( level );
72 }
73
75 {
76 wxLog::SetLogLevel( m_old_level );
77 }
78};
79
80
81bool EDA_PATTERN_MATCH_REGEX::SetPattern( const wxString& aPattern )
82{
83 if( aPattern.StartsWith( "^" ) && aPattern.EndsWith( "$" ) )
84 {
85 m_pattern = aPattern;
86 }
87 else if( aPattern.StartsWith( "/" ) )
88 {
89 // Requiring a '/' on the end means they get no feedback while they type
90 m_pattern = aPattern.Mid( 1 );
91
92 if( m_pattern.EndsWith( "/" ) )
93 m_pattern = m_pattern.Left( m_pattern.length() - 1 );
94 }
95 else
96 {
97 // For now regular expressions must be explicit
98 return false;
99 }
100
101 // Evil and undocumented: wxRegEx::Compile calls wxLogError on error, even
102 // though it promises to just return false. Silence the error.
103 WX_LOGLEVEL_CONTEXT ctx( wxLOG_FatalError );
104
105 return m_regex.Compile( m_pattern, wxRE_ADVANCED );
106}
107
108
109bool EDA_PATTERN_MATCH_REGEX_ANCHORED::SetPattern( const wxString& aPattern )
110{
111 wxString pattern( aPattern );
112
113 if( !pattern.StartsWith( wxT( "^" ) ) )
114 pattern = wxT( "^" ) + pattern;
115
116 if( !pattern.EndsWith( wxT( "$" ) ) )
117 pattern += wxT( "$" );
118
119 return EDA_PATTERN_MATCH_REGEX::SetPattern( pattern );
120}
121
122
124{
125 return m_pattern;
126}
127
128
130{
131 if( m_regex.IsValid() )
132 {
133 if( m_regex.Matches( aCandidate ) )
134 {
135 size_t start, len;
136 m_regex.GetMatch( &start, &len, 0 );
137
138 return { static_cast<int>( CLAMPED_VAL_INT_MAX( start ) ),
139 static_cast<int>( CLAMPED_VAL_INT_MAX( len ) ) };
140 }
141 else
142 {
143 return {};
144 }
145 }
146 else
147 {
148 int loc = aCandidate.Find( m_pattern );
149
150 if( loc == wxNOT_FOUND )
151 return {};
152 else
153 return { loc, static_cast<int>( m_pattern.size() ) };
154 }
155}
156
157
158bool EDA_PATTERN_MATCH_WILDCARD::SetPattern( const wxString& aPattern )
159{
160 m_wildcard_pattern = aPattern;
161
162 // Compile the wildcard string to a regular expression
163 wxString regex;
164 regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
165
166 const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
167
168 for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
169 {
170 wxUniChar c = *it;
171
172 if( c == '?' )
173 {
174 regex += wxT( "." );
175 }
176 else if( c == '*' )
177 {
178 regex += wxT( ".*" );
179 }
180 else if( to_replace.Find( c ) != wxNOT_FOUND )
181 {
182 regex += "\\";
183 regex += c;
184 }
185 else
186 {
187 regex += c;
188 }
189 }
190
191 return EDA_PATTERN_MATCH_REGEX::SetPattern( wxS( "/" ) + regex + wxS( "/" ) );
192}
193
194
196{
197 return m_wildcard_pattern;
198}
199
200
202{
203 return EDA_PATTERN_MATCH_REGEX::Find( aCandidate );
204}
205
206
208{
209 m_wildcard_pattern = aPattern;
210
211 // Compile the wildcard string to a regular expression
212 wxString regex;
213 regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
214
215 const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
216
217 regex += wxT( "^" );
218
219 for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
220 {
221 wxUniChar c = *it;
222
223 if( c == '?' )
224 {
225 regex += wxT( "." );
226 }
227 else if( c == '*' )
228 {
229 regex += wxT( ".*" );
230 }
231 else if( to_replace.Find( c ) != wxNOT_FOUND )
232 {
233 regex += wxS( "\\" );
234 regex += c;
235 }
236 else
237 {
238 regex += c;
239 }
240 }
241
242 regex += wxT( "$" );
243
245}
246
247
248bool EDA_PATTERN_MATCH_RELATIONAL::SetPattern( const wxString& aPattern )
249{
250 wxRegEx regex_search( R"(^(\w+)(<|<=|=|>=|>)([-+]?[\d.]*)(\w*)$)", wxRE_ADVANCED );
251
252 bool matches = regex_search.IsValid() && regex_search.Matches( aPattern );
253
254 if( !matches || regex_search.GetMatchCount() < 5 )
255 return false;
256
257 m_pattern = aPattern;
258 wxString key = regex_search.GetMatch( aPattern, 1 );
259 wxString rel = regex_search.GetMatch( aPattern, 2 );
260 wxString val = regex_search.GetMatch( aPattern, 3 );
261 wxString unit = regex_search.GetMatch( aPattern, 4 );
262
263 m_key = key.Lower();
264
265 if( rel == wxS( "<" ) )
266 m_relation = LT;
267 else if( rel == wxS( "<=" ) )
268 m_relation = LE;
269 else if( rel == wxS( "=" ) )
270 m_relation = EQ;
271 else if( rel == wxS( ">=" ) )
272 m_relation = GE;
273 else if( rel == wxS( ">" ) )
274 m_relation = GT;
275 else
276 return false;
277
278 if( val == "" )
279 {
280 // Matching on empty values keeps the match list from going empty when the user
281 // types the relational operator character, which helps prevent confusion.
282 m_relation = ANY;
283 }
284 else if( !val.ToCDouble( &m_value ) )
285 {
286 return false;
287 }
288
289 auto unit_it = m_units.find( unit.Lower() );
290
291 if( unit_it != m_units.end() )
292 m_value *= unit_it->second;
293 else
294 return false;
295
296 m_pattern = aPattern;
297
298 return true;
299}
300
301
303{
304 return m_pattern;
305}
306
307
309{
310 wxStringTokenizer tokenizer( aCandidate );
311 size_t lastpos = 0;
312
313 while( tokenizer.HasMoreTokens() )
314 {
315 const wxString token = tokenizer.GetNextToken();
316 int found_delta = FindOne( token );
317
318 if( found_delta != EDA_PATTERN_NOT_FOUND )
319 {
320 size_t found = (size_t) found_delta + lastpos;
321 return { static_cast<int>( CLAMPED_VAL_INT_MAX( found ) ), 0 };
322 }
323
324 lastpos = tokenizer.GetPosition();
325 }
326
327 return {};
328}
329
330
331int EDA_PATTERN_MATCH_RELATIONAL::FindOne( const wxString& aCandidate ) const
332{
333 wxRegEx regex_description( R"((\w+)[=:]([-+]?[\d.]+)(\w*))", wxRE_ADVANCED );
334
335 bool matches = regex_description.IsValid() && regex_description.Matches( aCandidate );
336
337 if( !matches )
339
340 size_t start, len;
341 regex_description.GetMatch( &start, &len, 0 );
342 wxString key = regex_description.GetMatch( aCandidate, 1 );
343 wxString val = regex_description.GetMatch( aCandidate, 2 );
344 wxString unit = regex_description.GetMatch( aCandidate, 3 );
345
346 int istart = static_cast<int>( CLAMPED_VAL_INT_MAX( start ) );
347
348 if( key.Lower() != m_key )
350
351 double val_parsed;
352
353 if( !val.ToCDouble( &val_parsed ) )
355
356 auto unit_it = m_units.find( unit.Lower() );
357
358 if( unit_it != m_units.end() )
359 val_parsed *= unit_it->second;
360
361 switch( m_relation )
362 {
363 case LT: return val_parsed < m_value ? istart : EDA_PATTERN_NOT_FOUND;
364 case LE: return val_parsed <= m_value ? istart : EDA_PATTERN_NOT_FOUND;
365 case EQ: return val_parsed == m_value ? istart : EDA_PATTERN_NOT_FOUND;
366 case GE: return val_parsed >= m_value ? istart : EDA_PATTERN_NOT_FOUND;
367 case GT: return val_parsed > m_value ? istart : EDA_PATTERN_NOT_FOUND;
368 case ANY: return istart;
369 default: return EDA_PATTERN_NOT_FOUND;
370 }
371}
372
373
374const std::map<wxString, double> EDA_PATTERN_MATCH_RELATIONAL::m_units = {
375 { wxS( "p" ), 1e-12 },
376 { wxS( "n" ), 1e-9 },
377 { wxS( "u" ), 1e-6 },
378 { wxS( "m" ), 1e-3 },
379 { wxS( "" ), 1. },
380 { wxS( "k" ), 1e3 },
381 { wxS( "meg" ), 1e6 },
382 { wxS( "g" ), 1e9 },
383 { wxS( "t" ), 1e12 },
384 { wxS( "ki" ), 1024. },
385 { wxS( "mi" ), 1048576. },
386 { wxS( "gi" ), 1073741824. },
387 { wxS( "ti" ), 1099511627776. } };
388
389
391 COMBINED_MATCHER_CONTEXT aContext ) :
392 m_pattern( aPattern )
393{
394 switch( aContext )
395 {
396 case CTX_LIBITEM:
397 // Whatever syntax users prefer, it shall be matched.
398 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
399 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
400 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_RELATIONAL>() );
401
402 // If any of the above matchers couldn't be created because the pattern
403 // syntax does not match, the substring will try its best.
404 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
405 break;
406
407 case CTX_NET:
408 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
409 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
410 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
411 break;
412
413 case CTX_NETCLASS:
414 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX_ANCHORED>() );
415 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD_ANCHORED>() );
416 break;
417
418 case CTX_SIGNAL:
419 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
420 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
421 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
422 break;
423
424 case CTX_SEARCH:
425 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
426 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
427 AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
428 break;
429 }
430}
431
432
433bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition )
434{
435 aPosition = EDA_PATTERN_NOT_FOUND;
436 aMatchersTriggered = 0;
437
438 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
439 {
440 EDA_PATTERN_MATCH::FIND_RESULT local_find = matcher->Find( aTerm );
441
442 if( local_find )
443 {
444 aMatchersTriggered += 1;
445
446 if( local_find.start < aPosition || aPosition == EDA_PATTERN_NOT_FOUND )
447 aPosition = local_find.start;
448 }
449 }
450
451 return aPosition != EDA_PATTERN_NOT_FOUND;
452}
453
454
455bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm )
456{
457 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
458 {
459 if( matcher->Find( aTerm ).start >= 0 )
460 return true;
461 }
462
463 return false;
464}
465
466
467bool EDA_COMBINED_MATCHER::StartsWith( const wxString& aTerm )
468{
469 for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
470 {
471 if( matcher->Find( aTerm ).start == 0 )
472 return true;
473 }
474
475 return false;
476}
477
478
479int EDA_COMBINED_MATCHER::ScoreTerms( std::vector<SEARCH_TERM>& aWeightedTerms )
480{
481 int score = 0;
482
483 for( SEARCH_TERM& term : aWeightedTerms )
484 {
485 if( !term.Normalized )
486 {
487 term.Text = term.Text.MakeLower().Trim( false ).Trim( true );
488
489 // Don't cause KiCad to hang if someone accidentally pastes the PCB or schematic
490 // into the search box.
491 if( term.Text.Length() > 1000 )
492 term.Text = term.Text.Left( 1000 );
493
494 term.Normalized = true;
495 }
496
497 int found_pos = EDA_PATTERN_NOT_FOUND;
498 int matchers_fired = 0;
499
500 if( GetPattern() == term.Text )
501 {
502 score += 8 * term.Score;
503 }
504 else if( Find( term.Text, matchers_fired, found_pos ) )
505 {
506 if( found_pos == 0 )
507 score += 2 * term.Score;
508 else
509 score += term.Score;
510 }
511 }
512
513 return score;
514}
515
516
517wxString const& EDA_COMBINED_MATCHER::GetPattern() const
518{
519 return m_pattern;
520}
521
522
523void EDA_COMBINED_MATCHER::AddMatcher( const wxString &aPattern,
524 std::unique_ptr<EDA_PATTERN_MATCH> aMatcher )
525{
526 if ( aMatcher->SetPattern( aPattern ) )
527 m_matchers.push_back( std::move( aMatcher ) );
528}
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)
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.