KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_rules_downgrade.h
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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#ifndef DRC_RULES_DOWNGRADE_H
21#define DRC_RULES_DOWNGRADE_H
22
23#include <cctype>
24#include <string>
25#include <vector>
26
27#include <wx/intl.h>
28
29#include <downgrade_scan.h>
30#include <downgrade_target.h>
31
32// The old rules parsers are all-or-nothing. One unknown keyword anywhere makes the target drop
33// every custom rule and refuse to run DRC. So the export filters the file: rules the target
34// cannot parse are removed and reported, surviving rules are kept byte-identical.
35
39static constexpr DATED_TOKEN DRC_RULE_TOKENS[] = {
40 // Constraints and disallow kinds added in 10.0
41 { "bridged_mask", 20260206 },
42 { "solder_mask_expansion", 20260206 },
43 { "solder_mask_sliver", 20260206 },
44 { "solder_paste_abs_margin", 20260206 },
45 { "solder_paste_rel_margin", 20260206 },
46 { "via_dangling", 20260206 },
47 { "through_via", 20260206 },
48 { "blind_via", 20260206 },
49 // Expression functions added in 10.0
50 { "isBlindVia", 20260206 },
51 { "isBuriedVia", 20260206 },
52 { "memberOfSheetOrChildren", 20260206 },
53 { "hasExactNetclass", 20260206 },
54 // Added after 10.0
55 { "net_chain_length", 99999999 },
56 { "stub_length", 99999999 },
57 { "return_path", 99999999 },
58 { "inNetChain", 99999999 },
59 { "hasNetChain", 99999999 },
60 { "inNetChainClass", 99999999 },
61 { "microvia_aspect_ratio", 99999999 },
62 { "microvia_stack_depth", 99999999 },
63 { "intersectsKeepout", 99999999 },
64 { "isStackedVia", 99999999 },
65 { "customProperty", 99999999 },
66 { "hasCustomProperty", 99999999 },
67 // Released readers test zone fills rather than the source's zone outlines.
68 { "intersectsArea", 99999999 },
69};
70
73static constexpr DATED_TOKEN DRC_RULE_PROPERTIES[] = {
74 // Released in 10.0
75 { "corner_radius", 20260206 },
76 { "fill", 20260206 },
77 { "auto_thickness", 20260206 },
78 { "target_delay", 20260206 },
79 { "target_skew_delay", 20260206 },
80 { "top_counterbore_depth", 20260206 },
81 { "top_countersink_angle", 20260206 },
82 { "bottom_counterbore_depth", 20260206 },
83 { "bottom_countersink_angle", 20260206 },
84 { "backdrill_mode", 20260206 },
85 { "bottom_backdrill_size", 20260206 },
86 { "top_backdrill_size", 20260206 },
87 { "pad_to_die_delay", 20260206 },
88 { "show_text", 20260206 },
89 { "text_size", 20260206 },
90 { "barcode_type", 20260206 },
91 { "error_correction", 20260206 },
92 { "margin_x", 20260206 },
93 { "margin_y", 20260206 },
94 { "column_width", 20260206 },
95 { "row_height", 20260206 },
96 { "capping", 20260206 },
97 { "filling", 20260206 },
98 { "keep_out_zone_fills", 20260206 },
99 { "hatch_orientation", 20260206 },
100 // Added after 10.0
101 { "major_radius", 99999999 },
102 { "minor_radius", 99999999 },
103 { "ellipse_rotation", 99999999 },
104 { "arc_start_angle", 99999999 },
105 { "arc_end_angle", 99999999 },
106 { "start_shape", 99999999 },
107 { "start_length", 99999999 },
108 { "start_width", 99999999 },
109 { "start_stroke_width", 99999999 },
110 { "end_shape", 99999999 },
111 { "end_length", 99999999 },
112 { "end_width", 99999999 },
113 { "end_stroke_width", 99999999 },
114 { "driving", 99999999 },
115 { "scale_x", 99999999 },
116 { "scale_y", 99999999 },
117 { "footprint_type", 99999999 },
118 { "exclude_from_simulation", 99999999 },
119 { "start_layer", 99999999 },
120 { "end_layer", 99999999 },
121 { "style", 99999999 },
122 { "use_netclass_values", 99999999 },
123 { "via_diameter", 99999999 },
124 { "via_hole", 99999999 },
125 { "pitch", 99999999 },
126 { "capped", 99999999 },
127 { "drill", 99999999 },
128 { "layout", 99999999 },
129 { "mode", 99999999 },
130 { "seed", 99999999 },
131 { "guarded_net", 99999999 },
132 { "simulation_electrical_type", 99999999 },
133 { "value_mode", 99999999 },
134 { "show_totals", 99999999 },
135 { "offset_x", 99999999 },
136 { "offset_y", 99999999 },
137 { "symbol_size", 99999999 },
138 { "hole_span", 99999999 },
139 { "outline_slots", 99999999 },
140 { "guide_cross", 99999999 },
141 { "decimal_places", 99999999 },
142 { "grid_type", 99999999 },
143 { "extent_x", 99999999 },
144 { "extent_y", 99999999 },
145 { "spacing_x", 99999999 },
146 { "spacing_y", 99999999 },
147 { "radius_extent", 99999999 },
148 { "radius_spacing", 99999999 },
149 { "phi_extent", 99999999 },
150 { "phi_spacing", 99999999 },
151 { "tick_interval", 99999999 },
152 { "affects_cursor_snap", 99999999 },
153 { "affects_routing", 99999999 },
154 { "affects_placement", 99999999 },
155 { "automatically_update_net", 99999999 },
156 { "front_tenting", 99999999 },
157 { "back_tenting", 99999999 },
158 { "front_covering", 99999999 },
159 { "back_covering", 99999999 },
160 { "front_plugging", 99999999 },
161 { "back_plugging", 99999999 },
162 { "thieving_pattern", 99999999 },
163 { "thieving_element_size", 99999999 },
164 { "thieving_gap", 99999999 },
165 { "thieving_line_width", 99999999 },
166 { "thieving_stagger", 99999999 },
167 { "thieving_orientation", 99999999 },
168};
169
170
172{
173 wxString m_text;
174 std::vector<wxString> m_dropped;
175 wxString m_error;
176};
177
178
180inline wxString ExtractDrcRuleName( const std::string& aBlock )
181{
182 size_t pos = aBlock.find( "(rule" );
183
184 if( pos == std::string::npos )
185 return wxEmptyString;
186
187 pos += 5;
188
189 while( pos < aBlock.size() && std::isspace( static_cast<unsigned char>( aBlock[pos] ) ) )
190 pos++;
191
192 if( pos >= aBlock.size() )
193 return wxEmptyString;
194
195 std::string name;
196
197 if( aBlock[pos] == '"' )
198 {
199 for( pos++; pos < aBlock.size() && aBlock[pos] != '"'; pos++ )
200 {
201 if( aBlock[pos] == '\\' && pos + 1 < aBlock.size() )
202 pos++;
203
204 name += aBlock[pos];
205 }
206 }
207 else
208 {
209 while( pos < aBlock.size() && !std::isspace( static_cast<unsigned char>( aBlock[pos] ) ) && aBlock[pos] != '('
210 && aBlock[pos] != ')' )
211 {
212 name += aBlock[pos++];
213 }
214 }
215
216 return wxString::FromUTF8( name.c_str() );
217}
218
219
222inline std::string StripDrcComments( const std::string& aText )
223{
224 std::string out;
225 bool inString = false;
226
227 for( size_t i = 0; i < aText.size(); i++ )
228 {
229 char c = aText[i];
230
231 if( inString )
232 {
233 if( c == '\\' && i + 1 < aText.size() )
234 {
235 out += c;
236 out += aText[++i];
237 continue;
238 }
239
240 if( c == '"' )
241 inString = false;
242
243 out += c;
244 continue;
245 }
246
247 if( c == '"' )
248 {
249 inString = true;
250 }
251 else if( c == '#' )
252 {
253 while( i < aText.size() && aText[i] != '\n' )
254 i++;
255
256 if( i >= aText.size() )
257 break;
258
259 c = '\n';
260 }
261
262 out += c;
263 }
264
265 return out;
266}
267
268
270inline bool DrcRuleUnsupportedByTarget( const std::string& aRawBlock, const DOWNGRADE_TARGET& aTarget )
271{
272 std::string block = StripDrcComments( aRawBlock );
273 std::string conditions;
274 std::string values;
275 std::vector<std::string> nodes;
276
277 for( size_t i = 0; i < block.size(); i++ )
278 {
279 if( block[i] == '(' )
280 {
281 size_t start = ++i;
282
283 while( i < block.size() && !std::isspace( static_cast<unsigned char>( block[i] ) ) && block[i] != '('
284 && block[i] != ')' )
285 i++;
286
287 std::string node = block.substr( start, i - start );
288
289 if( node == "constraint" )
290 {
291 size_t kind = i;
292
293 while( kind < block.size() && std::isspace( static_cast<unsigned char>( block[kind] ) ) )
294 kind++;
295
296 size_t end = kind;
297
298 while( end < block.size() && !std::isspace( static_cast<unsigned char>( block[end] ) )
299 && block[end] != '(' && block[end] != ')' )
300 {
301 end++;
302 }
303
304 if( block.substr( kind, end - kind ) == "assertion" )
305 node = "assertion";
306 }
307
308 nodes.push_back( std::move( node ) );
309 i--;
310 }
311 else if( block[i] == ')' )
312 {
313 if( !nodes.empty() )
314 nodes.pop_back();
315 }
316 else if( block[i] == '"' )
317 {
318 std::string value;
319
320 while( ++i < block.size() && block[i] != '"' )
321 {
322 if( block[i] == '\\' && i + 1 < block.size() )
323 i++;
324
325 value += block[i];
326 }
327
328 if( !nodes.empty() && ( nodes.back() == "condition" || nodes.back() == "assertion" ) )
329 {
330 char quote = 0;
331
332 for( size_t j = 0; j < value.size(); j++ )
333 {
334 char c = value[j];
335
336 if( quote )
337 {
338 if( c == '\\' && j + 1 < value.size() )
339 j++;
340 else if( c == quote )
341 quote = 0;
342 }
343 else if( c == '\'' || c == '"' )
344 {
345 quote = c;
346 conditions += ' ';
347 }
348 else
349 {
350 conditions += c;
351 }
352 }
353
354 conditions += '\n';
355 }
356 else if( !nodes.empty() && ( nodes.back() == "min" || nodes.back() == "max" || nodes.back() == "opt" ) )
357 {
358 values += value + '\n';
359 }
360 }
361 }
362
363 // Rule names are labels, including when the source uses an unquoted name.
364 size_t name = block.find( "(rule" );
365
366 if( name != std::string::npos )
367 {
368 name += 5;
369
370 while( name < block.size() && std::isspace( static_cast<unsigned char>( block[name] ) ) )
371 name++;
372
373 if( name < block.size() && block[name] != '"' )
374 {
375 size_t end = name;
376
377 while( end < block.size() && !std::isspace( static_cast<unsigned char>( block[end] ) ) && block[end] != '('
378 && block[end] != ')' )
379 end++;
380
381 block.replace( name, end - name, end - name, ' ' );
382 }
383 }
384
385 const std::string structural = StripSexprStrings( block );
386
387 std::string foldedConditions = conditions;
388
389 for( char& c : foldedConditions )
390 c = static_cast<char>( std::tolower( static_cast<unsigned char>( c ) ) );
391
392 for( const DATED_TOKEN& token : DRC_RULE_TOKENS )
393 {
394 if( aTarget.m_boardVersion >= token.m_introducedIn )
395 continue;
396
397 if( ContainsSexprValue( structural, token.m_token ) )
398 return true;
399
400 std::string function = token.m_token;
401
402 for( char& c : function )
403 c = static_cast<char>( std::tolower( static_cast<unsigned char>( c ) ) );
404
405 for( size_t pos = foldedConditions.find( function ); pos != std::string::npos;
406 pos = foldedConditions.find( function, pos + 1 ) )
407 {
408 if( pos > 0
409 && ( std::isalnum( static_cast<unsigned char>( foldedConditions[pos - 1] ) )
410 || foldedConditions[pos - 1] == '_' ) )
411 continue;
412
413 size_t after = pos + function.size();
414
415 while( after < foldedConditions.size()
416 && std::isspace( static_cast<unsigned char>( foldedConditions[after] ) ) )
417 after++;
418
419 if( after < foldedConditions.size() && foldedConditions[after] == '(' )
420 return true;
421 }
422 }
423
424 for( size_t pos = 0; pos < foldedConditions.size(); pos++ )
425 {
426 if( foldedConditions[pos] != '.' )
427 continue;
428
429 size_t begin = pos + 1;
430
431 while( begin < foldedConditions.size()
432 && std::isspace( static_cast<unsigned char>( foldedConditions[begin] ) ) )
433 {
434 begin++;
435 }
436
437 size_t end = begin;
438
439 while( end < foldedConditions.size()
440 && ( std::isalnum( static_cast<unsigned char>( foldedConditions[end] ) )
441 || foldedConditions[end] == '_' ) )
442 {
443 end++;
444 }
445
446 const std::string property = foldedConditions.substr( begin, end - begin );
447
448 // Parent navigation is new, but the old standalone Parent string property is supported.
449 if( property == "parent" )
450 {
451 size_t next = end;
452
453 while( next < foldedConditions.size()
454 && std::isspace( static_cast<unsigned char>( foldedConditions[next] ) ) )
455 {
456 next++;
457 }
458
459 if( next < foldedConditions.size() && foldedConditions[next] == '.' )
460 return true;
461 }
462
463 for( const DATED_TOKEN& token : DRC_RULE_PROPERTIES )
464 {
465 if( aTarget.m_boardVersion < token.m_introducedIn && property == token.m_token )
466 return true;
467 }
468
469 pos = end > begin ? end - 1 : pos;
470 }
471
472 // Time and angle value units appeared in 10.0.
473 if( aTarget.m_boardVersion < 20260206 )
474 {
475 auto isWordChar = []( char c )
476 {
477 return std::isalnum( static_cast<unsigned char>( c ) ) || c == '_';
478 };
479
480 const std::string numeric = structural + '\n' + values + conditions;
481
482 for( size_t i = 1; i + 1 < numeric.size(); i++ )
483 {
484 size_t before = i;
485
486 while( before > 0 && std::isspace( static_cast<unsigned char>( numeric[before - 1] ) ) )
487 before--;
488
489 if( before == 0
490 || ( !std::isdigit( static_cast<unsigned char>( numeric[before - 1] ) )
491 && numeric[before - 1] != '.' ) )
492 continue;
493
494 if( ( numeric[i] == 'f' || numeric[i] == 'p' ) && numeric[i + 1] == 's'
495 && ( i + 2 >= numeric.size() || !isWordChar( numeric[i + 2] ) ) )
496 {
497 return true;
498 }
499
500 if( numeric.compare( i, 3, "deg" ) == 0 && ( i + 3 >= numeric.size() || !isWordChar( numeric[i + 3] ) ) )
501 return true;
502 }
503 }
504
505 return false;
506}
507
508
510inline bool IsDrcCommentAtStartOfLine( const std::string& aText, size_t aOffset )
511{
512 while( aOffset > 0 && aText[aOffset - 1] != '\n' )
513 {
514 if( !std::isspace( static_cast<unsigned char>( aText[--aOffset] ) ) )
515 return false;
516 }
517
518 return true;
519}
520
521
525inline DRC_RULES_FILTER_RESULT FilterDrcRulesForTarget( const wxString& aRulesText, const DOWNGRADE_TARGET& aTarget )
526{
527 std::string text = aRulesText.ToStdString( wxConvUTF8 );
528
530 result.m_text = wxT( "(version 1)\n" );
531
532 size_t i = 0;
533
534 while( i < text.size() )
535 {
536 // A comment travels with the block after it, so a dropped rule takes its comment along.
537 size_t prefixStart = i;
538
539 while( i < text.size() && text[i] != '(' )
540 {
541 if( text[i] == '#' )
542 {
544 {
545 result.m_error = _( "Design rule comments must start on a separate line." );
546 return result;
547 }
548
549 while( i < text.size() && text[i] != '\n' )
550 i++;
551 }
552 else
553 {
554 i++;
555 }
556 }
557
558 if( i >= text.size() )
559 break;
560
561 std::string prefix = text.substr( prefixStart, i - prefixStart );
562
563 size_t blockStart = i;
564 int depth = 0;
565 bool inString = false;
566
567 for( ; i < text.size(); i++ )
568 {
569 char c = text[i];
570
571 if( inString )
572 {
573 if( c == '\\' && i + 1 < text.size() )
574 i++;
575 else if( c == '"' )
576 inString = false;
577
578 continue;
579 }
580
581 // Comments can contain unmatched parentheses, quotes, and escapes. Preserve
582 // their bytes in the block, but never interpret them as s-expression syntax.
583 if( c == '#' )
584 {
585 // Match DSNLEXER: inline comments are invalid in both target readers.
586 // Refuse them instead of claiming an invalid rules file was exported.
588 {
589 result.m_error = _( "Design rule comments must start on a separate line." );
590 return result;
591 }
592
593 while( i < text.size() && text[i] != '\n' )
594 i++;
595 }
596 else if( c == '"' )
597 inString = true;
598 else if( c == '(' )
599 depth++;
600 else if( c == ')' && --depth == 0 )
601 {
602 i++;
603 break;
604 }
605 }
606
607 std::string block = text.substr( blockStart, i - blockStart );
608
609 if( depth != 0 || inString )
610 {
611 result.m_error = _( "Unbalanced parentheses or unterminated string in design rules." );
612 return result;
613 }
614
615 // The version header is rewritten, never copied.
616 if( block.compare( 0, 8, "(version" ) == 0 )
617 continue;
618
619 bool isRule = block.compare( 0, 5, "(rule" ) == 0
620 && ( block.size() <= 5
621 || ( !std::isalnum( static_cast<unsigned char>( block[5] ) ) && block[5] != '_' ) );
622
623 if( !isRule || DrcRuleUnsupportedByTarget( block, aTarget ) )
624 {
625 result.m_dropped.push_back( ExtractDrcRuleName( block ) );
626 continue;
627 }
628
629 result.m_text += wxString::FromUTF8( ( prefix + block ).c_str() );
630 }
631
632 result.m_text += wxT( "\n" );
633
634 return result;
635}
636
637#endif // DRC_RULES_DOWNGRADE_H
const char * name
std::string StripSexprStrings(const std::string &aText)
Drop quoted strings so a token inside a text value cannot cause a false refusal.
bool ContainsSexprValue(const std::string &aText, const std::string &aToken)
Some features are written as a bareword value, not a node head, so the node scan cannot see them.
bool DrcRuleUnsupportedByTarget(const std::string &aRawBlock, const DOWNGRADE_TARGET &aTarget)
True if the rule text uses something the target's parser or evaluator cannot handle.
wxString ExtractDrcRuleName(const std::string &aBlock)
The rule name following a "(rule" head, for the report.
static constexpr DATED_TOKEN DRC_RULE_PROPERTIES[]
Property names exposed by PCB expressions, normalized like the evaluator's underscore aliases.
std::string StripDrcComments(const std::string &aText)
Drop # comments, keeping quoted strings intact, so a comment cannot trip the token or unit scans belo...
static constexpr DATED_TOKEN DRC_RULE_TOKENS[]
Rule-file tokens dated by the board format of the first release that parses them.
DRC_RULES_FILTER_RESULT FilterDrcRulesForTarget(const wxString &aRulesText, const DOWNGRADE_TARGET &aTarget)
Filter a .kicad_dru file for the target.
bool IsDrcCommentAtStartOfLine(const std::string &aText, size_t aOffset)
Match the full-line comment syntax of DSNLEXER, used by both target readers.
#define _(s)
bool isWordChar(const wxUniChar &c)
Definition eda_item.cpp:312
CITER next(CITER it)
Definition ptree.cpp:120
A token dated by the format that introduced it, for the fail-closed downgrade gates.
An older KiCad release we can export a project back to.
int m_boardVersion
SEXPR_BOARD_FILE_VERSION for the target.
wxString m_text
The filtered rules file text.
wxString m_error
Invalid source syntax; do not write m_text.
std::vector< wxString > m_dropped
Names of the rules the target cannot parse.
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.