You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

513 lines
14 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2015-2017 Chris Pavlina <pavlina.chris@gmail.com>
  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. #include <eda_pattern_match.h>
  25. #include <limits>
  26. #include <wx/log.h>
  27. #include <wx/tokenzr.h>
  28. #include <algorithm>
  29. // Helper to make the code cleaner when we want this operation
  30. #define CLAMPED_VAL_INT_MAX( x ) std::min( x, static_cast<size_t>( std::numeric_limits<int>::max() ) )
  31. bool EDA_PATTERN_MATCH_SUBSTR::SetPattern( const wxString& aPattern )
  32. {
  33. m_pattern = aPattern;
  34. return true;
  35. }
  36. wxString const& EDA_PATTERN_MATCH_SUBSTR::GetPattern() const
  37. {
  38. return m_pattern;
  39. }
  40. EDA_PATTERN_MATCH::FIND_RESULT EDA_PATTERN_MATCH_SUBSTR::Find( const wxString& aCandidate ) const
  41. {
  42. int loc = aCandidate.Find( m_pattern );
  43. if( loc == wxNOT_FOUND )
  44. return {};
  45. else
  46. return { loc, static_cast<int>( m_pattern.size() ) };
  47. }
  48. /**
  49. * Context class to set wx loglevel for a block, and always restore it at the end.
  50. */
  51. class WX_LOGLEVEL_CONTEXT
  52. {
  53. wxLogLevel m_old_level;
  54. public:
  55. WX_LOGLEVEL_CONTEXT( wxLogLevel level )
  56. {
  57. m_old_level = wxLog::GetLogLevel();
  58. wxLog::SetLogLevel( level );
  59. }
  60. ~WX_LOGLEVEL_CONTEXT()
  61. {
  62. wxLog::SetLogLevel( m_old_level );
  63. }
  64. };
  65. bool EDA_PATTERN_MATCH_REGEX::SetPattern( const wxString& aPattern )
  66. {
  67. if( aPattern.StartsWith( "^" ) && aPattern.EndsWith( "$" ) )
  68. {
  69. m_pattern = aPattern;
  70. }
  71. else if( aPattern.StartsWith( "/" ) )
  72. {
  73. // Requiring a '/' on the end means they get no feedback while they type
  74. m_pattern = aPattern.Mid( 1 );
  75. if( m_pattern.EndsWith( "/" ) )
  76. m_pattern = m_pattern.Left( m_pattern.length() - 1 );
  77. }
  78. else
  79. {
  80. // For now regular expressions must be explicit
  81. return false;
  82. }
  83. // Evil and undocumented: wxRegEx::Compile calls wxLogError on error, even
  84. // though it promises to just return false. Silence the error.
  85. WX_LOGLEVEL_CONTEXT ctx( wxLOG_FatalError );
  86. return m_regex.Compile( m_pattern, wxRE_ADVANCED );
  87. }
  88. bool EDA_PATTERN_MATCH_REGEX_ANCHORED::SetPattern( const wxString& aPattern )
  89. {
  90. wxString pattern( aPattern );
  91. if( !pattern.StartsWith( wxT( "^" ) ) )
  92. pattern = wxT( "^" ) + pattern;
  93. if( !pattern.EndsWith( wxT( "$" ) ) )
  94. pattern += wxT( "$" );
  95. return EDA_PATTERN_MATCH_REGEX::SetPattern( pattern );
  96. }
  97. wxString const& EDA_PATTERN_MATCH_REGEX::GetPattern() const
  98. {
  99. return m_pattern;
  100. }
  101. EDA_PATTERN_MATCH::FIND_RESULT EDA_PATTERN_MATCH_REGEX::Find( const wxString& aCandidate ) const
  102. {
  103. if( m_regex.IsValid() )
  104. {
  105. if( m_regex.Matches( aCandidate ) )
  106. {
  107. size_t start, len;
  108. m_regex.GetMatch( &start, &len, 0 );
  109. return { static_cast<int>( CLAMPED_VAL_INT_MAX( start ) ),
  110. static_cast<int>( CLAMPED_VAL_INT_MAX( len ) ) };
  111. }
  112. else
  113. {
  114. return {};
  115. }
  116. }
  117. else
  118. {
  119. int loc = aCandidate.Find( m_pattern );
  120. if( loc == wxNOT_FOUND )
  121. return {};
  122. else
  123. return { loc, static_cast<int>( m_pattern.size() ) };
  124. }
  125. }
  126. bool EDA_PATTERN_MATCH_WILDCARD::SetPattern( const wxString& aPattern )
  127. {
  128. m_wildcard_pattern = aPattern;
  129. // Compile the wildcard string to a regular expression
  130. wxString regex;
  131. regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
  132. const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
  133. for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
  134. {
  135. wxUniChar c = *it;
  136. if( c == '?' )
  137. {
  138. regex += wxT( "." );
  139. }
  140. else if( c == '*' )
  141. {
  142. regex += wxT( ".*" );
  143. }
  144. else if( to_replace.Find( c ) != wxNOT_FOUND )
  145. {
  146. regex += "\\";
  147. regex += c;
  148. }
  149. else
  150. {
  151. regex += c;
  152. }
  153. }
  154. return EDA_PATTERN_MATCH_REGEX::SetPattern( wxS( "/" ) + regex + wxS( "/" ) );
  155. }
  156. wxString const& EDA_PATTERN_MATCH_WILDCARD::GetPattern() const
  157. {
  158. return m_wildcard_pattern;
  159. }
  160. EDA_PATTERN_MATCH::FIND_RESULT EDA_PATTERN_MATCH_WILDCARD::Find( const wxString& aCandidate ) const
  161. {
  162. return EDA_PATTERN_MATCH_REGEX::Find( aCandidate );
  163. }
  164. bool EDA_PATTERN_MATCH_WILDCARD_ANCHORED::SetPattern( const wxString& aPattern )
  165. {
  166. m_wildcard_pattern = aPattern;
  167. // Compile the wildcard string to a regular expression
  168. wxString regex;
  169. regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly
  170. const wxString to_replace = wxT( ".*+?^${}()|[]/\\" );
  171. regex += wxT( "^" );
  172. for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it )
  173. {
  174. wxUniChar c = *it;
  175. if( c == '?' )
  176. {
  177. regex += wxT( "." );
  178. }
  179. else if( c == '*' )
  180. {
  181. regex += wxT( ".*" );
  182. }
  183. else if( to_replace.Find( c ) != wxNOT_FOUND )
  184. {
  185. regex += wxS( "\\" );
  186. regex += c;
  187. }
  188. else
  189. {
  190. regex += c;
  191. }
  192. }
  193. regex += wxT( "$" );
  194. return EDA_PATTERN_MATCH_REGEX::SetPattern( regex );
  195. }
  196. bool EDA_PATTERN_MATCH_RELATIONAL::SetPattern( const wxString& aPattern )
  197. {
  198. bool matches = m_regex_search.Matches( aPattern );
  199. if( !matches || m_regex_search.GetMatchCount() < 5 )
  200. return false;
  201. m_pattern = aPattern;
  202. wxString key = m_regex_search.GetMatch( aPattern, 1 );
  203. wxString rel = m_regex_search.GetMatch( aPattern, 2 );
  204. wxString val = m_regex_search.GetMatch( aPattern, 3 );
  205. wxString unit = m_regex_search.GetMatch( aPattern, 4 );
  206. m_key = key.Lower();
  207. if( rel == wxS( "<" ) )
  208. m_relation = LT;
  209. else if( rel == wxS( "<=" ) )
  210. m_relation = LE;
  211. else if( rel == wxS( "=" ) )
  212. m_relation = EQ;
  213. else if( rel == wxS( ">=" ) )
  214. m_relation = GE;
  215. else if( rel == wxS( ">" ) )
  216. m_relation = GT;
  217. else
  218. return false;
  219. if( val == "" )
  220. {
  221. // Matching on empty values keeps the match list from going empty when the user
  222. // types the relational operator character, which helps prevent confusion.
  223. m_relation = ANY;
  224. }
  225. else if( !val.ToCDouble( &m_value ) )
  226. {
  227. return false;
  228. }
  229. auto unit_it = m_units.find( unit.Lower() );
  230. if( unit_it != m_units.end() )
  231. m_value *= unit_it->second;
  232. else
  233. return false;
  234. m_pattern = aPattern;
  235. return true;
  236. }
  237. wxString const& EDA_PATTERN_MATCH_RELATIONAL::GetPattern() const
  238. {
  239. return m_pattern;
  240. }
  241. EDA_PATTERN_MATCH::FIND_RESULT EDA_PATTERN_MATCH_RELATIONAL::Find( const wxString& aCandidate ) const
  242. {
  243. wxStringTokenizer tokenizer( aCandidate );
  244. size_t lastpos = 0;
  245. while( tokenizer.HasMoreTokens() )
  246. {
  247. const wxString token = tokenizer.GetNextToken();
  248. int found_delta = FindOne( token );
  249. if( found_delta != EDA_PATTERN_NOT_FOUND )
  250. {
  251. size_t found = (size_t) found_delta + lastpos;
  252. return { static_cast<int>( CLAMPED_VAL_INT_MAX( found ) ), 0 };
  253. }
  254. lastpos = tokenizer.GetPosition();
  255. }
  256. return {};
  257. }
  258. int EDA_PATTERN_MATCH_RELATIONAL::FindOne( const wxString& aCandidate ) const
  259. {
  260. bool matches = m_regex_description.Matches( aCandidate );
  261. if( !matches )
  262. return EDA_PATTERN_NOT_FOUND;
  263. size_t start, len;
  264. m_regex_description.GetMatch( &start, &len, 0 );
  265. wxString key = m_regex_description.GetMatch( aCandidate, 1 );
  266. wxString val = m_regex_description.GetMatch( aCandidate, 2 );
  267. wxString unit = m_regex_description.GetMatch( aCandidate, 3 );
  268. int istart = static_cast<int>( CLAMPED_VAL_INT_MAX( start ) );
  269. if( key.Lower() != m_key )
  270. return EDA_PATTERN_NOT_FOUND;
  271. double val_parsed;
  272. if( !val.ToCDouble( &val_parsed ) )
  273. return EDA_PATTERN_NOT_FOUND;
  274. auto unit_it = m_units.find( unit.Lower() );
  275. if( unit_it != m_units.end() )
  276. val_parsed *= unit_it->second;
  277. switch( m_relation )
  278. {
  279. case LT: return val_parsed < m_value ? istart : EDA_PATTERN_NOT_FOUND;
  280. case LE: return val_parsed <= m_value ? istart : EDA_PATTERN_NOT_FOUND;
  281. case EQ: return val_parsed == m_value ? istart : EDA_PATTERN_NOT_FOUND;
  282. case GE: return val_parsed >= m_value ? istart : EDA_PATTERN_NOT_FOUND;
  283. case GT: return val_parsed > m_value ? istart : EDA_PATTERN_NOT_FOUND;
  284. case ANY: return istart;
  285. default: return EDA_PATTERN_NOT_FOUND;
  286. }
  287. }
  288. wxRegEx EDA_PATTERN_MATCH_RELATIONAL::m_regex_description(
  289. R"((\w+)[=:]([-+]?[\d.]+)(\w*))", wxRE_ADVANCED );
  290. wxRegEx EDA_PATTERN_MATCH_RELATIONAL::m_regex_search(
  291. R"(^(\w+)(<|<=|=|>=|>)([-+]?[\d.]*)(\w*)$)", wxRE_ADVANCED );
  292. const std::map<wxString, double> EDA_PATTERN_MATCH_RELATIONAL::m_units = {
  293. { wxS( "p" ), 1e-12 },
  294. { wxS( "n" ), 1e-9 },
  295. { wxS( "u" ), 1e-6 },
  296. { wxS( "m" ), 1e-3 },
  297. { wxS( "" ), 1. },
  298. { wxS( "k" ), 1e3 },
  299. { wxS( "meg" ), 1e6 },
  300. { wxS( "g" ), 1e9 },
  301. { wxS( "t" ), 1e12 },
  302. { wxS( "ki" ), 1024. },
  303. { wxS( "mi" ), 1048576. },
  304. { wxS( "gi" ), 1073741824. },
  305. { wxS( "ti" ), 1099511627776. } };
  306. EDA_COMBINED_MATCHER::EDA_COMBINED_MATCHER( const wxString& aPattern,
  307. COMBINED_MATCHER_CONTEXT aContext ) :
  308. m_pattern( aPattern )
  309. {
  310. switch( aContext )
  311. {
  312. case CTX_LIBITEM:
  313. // Whatever syntax users prefer, it shall be matched.
  314. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
  315. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
  316. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_RELATIONAL>() );
  317. // If any of the above matchers couldn't be created because the pattern
  318. // syntax does not match, the substring will try its best.
  319. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
  320. break;
  321. case CTX_NETCLASS:
  322. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX_ANCHORED>() );
  323. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD_ANCHORED>() );
  324. break;
  325. case CTX_SIGNAL:
  326. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
  327. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
  328. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
  329. break;
  330. case CTX_SEARCH:
  331. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
  332. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
  333. AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
  334. break;
  335. }
  336. }
  337. bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition )
  338. {
  339. aPosition = EDA_PATTERN_NOT_FOUND;
  340. aMatchersTriggered = 0;
  341. for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
  342. {
  343. EDA_PATTERN_MATCH::FIND_RESULT local_find = matcher->Find( aTerm );
  344. if( local_find )
  345. {
  346. aMatchersTriggered += 1;
  347. if( local_find.start < aPosition || aPosition == EDA_PATTERN_NOT_FOUND )
  348. aPosition = local_find.start;
  349. }
  350. }
  351. return aPosition != EDA_PATTERN_NOT_FOUND;
  352. }
  353. bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm )
  354. {
  355. for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
  356. {
  357. if( matcher->Find( aTerm ).start >= 0 )
  358. return true;
  359. }
  360. return false;
  361. }
  362. bool EDA_COMBINED_MATCHER::StartsWith( const wxString& aTerm )
  363. {
  364. for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
  365. {
  366. if( matcher->Find( aTerm ).start == 0 )
  367. return true;
  368. }
  369. return false;
  370. }
  371. int EDA_COMBINED_MATCHER::ScoreTerms( std::vector<SEARCH_TERM>& aWeightedTerms )
  372. {
  373. int score = 0;
  374. for( SEARCH_TERM& term : aWeightedTerms )
  375. {
  376. if( !term.Normalized )
  377. {
  378. term.Text = term.Text.MakeLower().Trim( false ).Trim( true );
  379. term.Normalized = true;
  380. }
  381. int found_pos = EDA_PATTERN_NOT_FOUND;
  382. int matchers_fired = 0;
  383. if( GetPattern() == term.Text )
  384. {
  385. score += 8 * term.Score;
  386. }
  387. else if( Find( term.Text, matchers_fired, found_pos ) )
  388. {
  389. if( found_pos == 0 )
  390. score += 2 * term.Score;
  391. else
  392. score += term.Score;
  393. }
  394. }
  395. return score;
  396. }
  397. wxString const& EDA_COMBINED_MATCHER::GetPattern() const
  398. {
  399. return m_pattern;
  400. }
  401. void EDA_COMBINED_MATCHER::AddMatcher( const wxString &aPattern,
  402. std::unique_ptr<EDA_PATTERN_MATCH> aMatcher )
  403. {
  404. if ( aMatcher->SetPattern( aPattern ) )
  405. m_matchers.push_back( std::move( aMatcher ) );
  406. }