KiCad PCB EDA Suite
drc_rule_parser.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) 2020-2021 KiCad Developers, see change_log.txt for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU 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, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 
25 #include <board.h>
26 #include <drc/drc_rule_parser.h>
27 #include <drc/drc_rule_condition.h>
28 #include <drc_rules_lexer.h>
29 #include <pcb_expr_evaluator.h>
30 #include <reporter.h>
31 
32 using namespace DRCRULE_T;
33 
34 
35 DRC_RULES_PARSER::DRC_RULES_PARSER( const wxString& aSource, const wxString& aSourceDescr ) :
36  DRC_RULES_LEXER( aSource.ToStdString(), aSourceDescr ),
37  m_requiredVersion( 0 ),
38  m_tooRecent( false ),
39  m_reporter( nullptr )
40 {
41 }
42 
43 
44 DRC_RULES_PARSER::DRC_RULES_PARSER( FILE* aFile, const wxString& aFilename ) :
45  DRC_RULES_LEXER( aFile, aFilename ),
46  m_requiredVersion( 0 ),
47  m_tooRecent( false ),
48  m_reporter( nullptr )
49 {
50 }
51 
52 
53 void DRC_RULES_PARSER::reportError( const wxString& aMessage )
54 {
55  wxString rest;
56  wxString first = aMessage.BeforeFirst( '|', &rest );
57 
58  if( m_reporter )
59  {
60  wxString msg = wxString::Format( _( "ERROR: <a href='%d:%d'>%s</a>%s" ), CurLineNumber(),
61  CurOffset(), first, rest );
62 
64  }
65  else
66  {
67  wxString msg = wxString::Format( _( "ERROR: %s%s" ), first, rest );
68 
69  THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
70  }
71 }
72 
73 
75 {
76  int depth = 1;
77 
78  for( T token = NextTok(); token != T_EOF; token = NextTok() )
79  {
80  if( token == T_LEFT )
81  depth++;
82 
83  if( token == T_RIGHT )
84  {
85  if( --depth == 0 )
86  break;
87  }
88  }
89 }
90 
91 
92 void DRC_RULES_PARSER::Parse( std::vector<DRC_RULE*>& aRules, REPORTER* aReporter )
93 {
94  bool haveVersion = false;
95  wxString msg;
96 
97  m_reporter = aReporter;
98 
99  for( T token = NextTok(); token != T_EOF; token = NextTok() )
100  {
101  if( token != T_LEFT )
102  reportError( _( "Missing '('." ) );
103 
104  token = NextTok();
105 
106  if( !haveVersion && token != T_version )
107  {
108  reportError( _( "Missing version statement." ) );
109  haveVersion = true; // don't keep on reporting it
110  }
111 
112  switch( token )
113  {
114  case T_version:
115  haveVersion = true;
116  token = NextTok();
117 
118  if( (int) token == DSN_RIGHT )
119  {
120  reportError( _( "Missing version number." ) );
121  break;
122  }
123 
124  if( (int) token == DSN_NUMBER )
125  {
126  m_requiredVersion = (int)strtol( CurText(), nullptr, 10 );
128  token = NextTok();
129  }
130  else
131  {
132  msg.Printf( _( "Unrecognized item '%s'.| Expected version number." ),
133  FromUTF8() );
134  reportError( msg );
135  }
136 
137  if( (int) token != DSN_RIGHT )
138  {
139  msg.Printf( _( "Unrecognized item '%s'." ),
140  FromUTF8() );
141  reportError( msg );
142  parseUnknown();
143  }
144 
145  break;
146 
147  case T_rule:
148  aRules.push_back( parseDRC_RULE() );
149  break;
150 
151  case T_EOF:
152  reportError( _( "Incomplete statement." ) );
153  break;
154 
155  default:
156  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ), FromUTF8(),
157  "'rule', 'version'" );
158  reportError( msg );
159  parseUnknown();
160  }
161  }
162 
163  if( m_reporter && !m_reporter->HasMessage() )
164  m_reporter->Report( _( "No errors found." ), RPT_SEVERITY_INFO );
165 
166  m_reporter = nullptr;
167 }
168 
169 
171 {
172  DRC_RULE* rule = new DRC_RULE();
173  T token = NextTok();
174  wxString msg;
175 
176  if( !IsSymbol( token ) )
177  reportError( _( "Missing rule name." ) );
178 
179  rule->m_Name = FromUTF8();
180 
181  for( token = NextTok(); token != T_RIGHT && token != T_EOF; token = NextTok() )
182  {
183  if( token != T_LEFT )
184  reportError( _( "Missing '('." ) );
185 
186  token = NextTok();
187 
188  switch( token )
189  {
190  case T_constraint:
191  parseConstraint( rule );
192  break;
193 
194  case T_condition:
195  token = NextTok();
196 
197  if( (int) token == DSN_RIGHT )
198  {
199  reportError( _( "Missing condition expression." ) );
200  break;
201  }
202 
203  if( IsSymbol( token ) )
204  {
205  rule->m_Condition = new DRC_RULE_CONDITION( FromUTF8() );
206  rule->m_Condition->Compile( m_reporter, CurLineNumber(), CurOffset() );
207  }
208  else
209  {
210  msg.Printf( _( "Unrecognized item '%s'.| Expected quoted expression." ),
211  FromUTF8() );
212  reportError( msg );
213  }
214 
215  if( (int) NextTok() != DSN_RIGHT )
216  {
217  reportError( wxString::Format( _( "Unrecognized item '%s'." ), FromUTF8() ) );
218  parseUnknown();
219  }
220 
221  break;
222 
223  case T_layer:
224  rule->m_LayerSource = FromUTF8();
225  rule->m_LayerCondition = parseLayer();
226  break;
227 
228  case T_EOF:
229  reportError( _( "Incomplete statement." ) );
230  return rule;
231 
232  default:
233  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ), FromUTF8(),
234  "'constraint', 'condition', 'disallow'" );
235  reportError( msg );
236  parseUnknown();
237  }
238  }
239 
240  if( (int) CurTok() != DSN_RIGHT )
241  reportError( _( "Missing ')'." ) );
242 
243  return rule;
244 }
245 
246 
248 {
249  DRC_CONSTRAINT constraint;
250 
251  int value;
252  wxString msg;
253 
254  T token = NextTok();
255 
256  if( (int) token == DSN_RIGHT || token == T_EOF )
257  {
258  msg.Printf( _( "Missing constraint type.| Expected %s." ),
259  "'clearance', 'hole_clearance', 'edge_clearance', 'hole', 'hole_to_hole', "
260  "'courtyard_clearance', 'silk_clearance', 'track_width', 'annular_width', "
261  "'disallow', 'length', 'skew', 'via_count', 'diff_pair_gap' or "
262  "'diff_pair_uncoupled'" );
263  reportError( msg );
264  return;
265  }
266 
267  switch( token )
268  {
269  case T_clearance: constraint.m_Type = CLEARANCE_CONSTRAINT; break;
270  case T_hole_clearance: constraint.m_Type = HOLE_CLEARANCE_CONSTRAINT; break;
271  case T_edge_clearance: constraint.m_Type = EDGE_CLEARANCE_CONSTRAINT; break;
272  case T_hole: constraint.m_Type = HOLE_SIZE_CONSTRAINT; break;
273  case T_hole_to_hole: constraint.m_Type = HOLE_TO_HOLE_CONSTRAINT; break;
274  case T_courtyard_clearance: constraint.m_Type = COURTYARD_CLEARANCE_CONSTRAINT; break;
275  case T_silk_clearance: constraint.m_Type = SILK_CLEARANCE_CONSTRAINT; break;
276  case T_track_width: constraint.m_Type = TRACK_WIDTH_CONSTRAINT; break;
277  case T_annular_width: constraint.m_Type = ANNULAR_WIDTH_CONSTRAINT; break;
278  case T_disallow: constraint.m_Type = DISALLOW_CONSTRAINT; break;
279  case T_length: constraint.m_Type = LENGTH_CONSTRAINT; break;
280  case T_skew: constraint.m_Type = SKEW_CONSTRAINT; break;
281  case T_via_count: constraint.m_Type = VIA_COUNT_CONSTRAINT; break;
282  case T_diff_pair_gap: constraint.m_Type = DIFF_PAIR_GAP_CONSTRAINT; break;
283  case T_diff_pair_uncoupled: constraint.m_Type = DIFF_PAIR_MAX_UNCOUPLED_CONSTRAINT; break;
284  default:
285  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ), FromUTF8(),
286  "'clearance', 'hole_clearance', 'edge_clearance', 'hole', hole_to_hole',"
287  "'courtyard_clearance', 'silk_clearance', 'track_width', 'annular_width', "
288  "'disallow', 'length', 'skew', 'diff_pair_gap' or 'diff_pair_uncoupled'." );
289  reportError( msg );
290  }
291 
292  if( constraint.m_Type == DISALLOW_CONSTRAINT )
293  {
294  for( token = NextTok(); token != T_RIGHT; token = NextTok() )
295  {
296  if( (int) token == DSN_STRING )
297  token = GetCurStrAsToken();
298 
299  switch( token )
300  {
301  case T_track: constraint.m_DisallowFlags |= DRC_DISALLOW_TRACKS; break;
302  case T_via: constraint.m_DisallowFlags |= DRC_DISALLOW_VIAS; break;
303  case T_micro_via: constraint.m_DisallowFlags |= DRC_DISALLOW_MICRO_VIAS; break;
304  case T_buried_via: constraint.m_DisallowFlags |= DRC_DISALLOW_BB_VIAS; break;
305  case T_pad: constraint.m_DisallowFlags |= DRC_DISALLOW_PADS; break;
306  case T_zone: constraint.m_DisallowFlags |= DRC_DISALLOW_ZONES; break;
307  case T_text: constraint.m_DisallowFlags |= DRC_DISALLOW_TEXTS; break;
308  case T_graphic: constraint.m_DisallowFlags |= DRC_DISALLOW_GRAPHICS; break;
309  case T_hole: constraint.m_DisallowFlags |= DRC_DISALLOW_HOLES; break;
310  case T_footprint: constraint.m_DisallowFlags |= DRC_DISALLOW_FOOTPRINTS; break;
311 
312  case T_EOF:
313  reportError( _( "Missing ')'." ) );
314  return;
315 
316  default:
317  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ), FromUTF8(),
318  "'track', 'via', 'micro_via', 'buried_via', 'pad', 'zone', 'text', "
319  "'graphic', 'hole' or 'footprint'." );
320  reportError( msg );
321  break;
322  }
323  }
324 
325  if( (int) CurTok() != DSN_RIGHT )
326  reportError( _( "Missing ')'." ) );
327 
328  aRule->AddConstraint( constraint );
329  return;
330  }
331 
332  for( token = NextTok(); token != T_RIGHT && token != T_EOF; token = NextTok() )
333  {
334  if( token != T_LEFT )
335  reportError( _( "Missing '('." ) );
336 
337  token = NextTok();
338 
339  switch( token )
340  {
341  case T_min:
342  token = NextTok();
343 
344  if( (int) token == DSN_RIGHT )
345  {
346  reportError( _( "Missing min value." ) );
347  break;
348  }
349 
350  parseValueWithUnits( FromUTF8(), value );
351  constraint.m_Value.SetMin( value );
352 
353  if( (int) NextTok() != DSN_RIGHT )
354  {
355  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
356  FromUTF8() ) );
357  parseUnknown();
358  }
359 
360  break;
361 
362  case T_max:
363  token = NextTok();
364 
365  if( (int) token == DSN_RIGHT )
366  {
367  reportError( _( "Missing max value." ) );
368  break;
369  }
370 
371  parseValueWithUnits( FromUTF8(), value );
372  constraint.m_Value.SetMax( value );
373 
374  if( (int) NextTok() != DSN_RIGHT )
375  {
376  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
377  FromUTF8() ) );
378  parseUnknown();
379  }
380 
381  break;
382 
383  case T_opt:
384  token = NextTok();
385 
386  if( (int) token == DSN_RIGHT )
387  {
388  reportError( _( "Missing opt value." ) );
389  break;
390  }
391 
392  parseValueWithUnits( FromUTF8(), value );
393  constraint.m_Value.SetOpt( value );
394 
395  if( (int) NextTok() != DSN_RIGHT )
396  {
397  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
398  FromUTF8() ) );
399  parseUnknown();
400  }
401 
402  break;
403 
404  case T_EOF:
405  reportError( _( "Incomplete statement." ) );
406  return;
407 
408  default:
409  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ),
410  FromUTF8(),
411  "'min', 'max', 'opt'" );
412  reportError( msg );
413  parseUnknown();
414  }
415  }
416 
417  if( (int) CurTok() != DSN_RIGHT )
418  reportError( _( "Missing ')'." ) );
419 
420  aRule->AddConstraint( constraint );
421 }
422 
423 
424 void DRC_RULES_PARSER::parseValueWithUnits( const wxString& aExpr, int& aResult )
425 {
426  auto errorHandler = [&]( const wxString& aMessage, int aOffset )
427  {
428  wxString rest;
429  wxString first = aMessage.BeforeFirst( '|', &rest );
430 
431  if( m_reporter )
432  {
433  wxString msg = wxString::Format( _( "ERROR: <a href='%d:%d'>%s</a>%s" ),
434  CurLineNumber(), CurOffset() + aOffset, first, rest );
435 
437  }
438  else
439  {
440  wxString msg = wxString::Format( _( "ERROR: %s%s" ), first, rest );
441 
442  THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(),
443  CurOffset() + aOffset );
444  }
445  };
446 
447  PCB_EXPR_EVALUATOR evaluator;
448  evaluator.SetErrorCallback( errorHandler );
449 
450  evaluator.Evaluate( aExpr );
451  aResult = evaluator.Result();
452 }
453 
454 
456 {
457  LSET retVal;
458  int token = NextTok();
459 
460  if( (int) token == DSN_RIGHT )
461  {
462  reportError( _( "Missing layer name or type." ) );
463  return LSET::AllCuMask();
464  }
465  else if( token == T_outer )
466  {
467  retVal = LSET::ExternalCuMask();
468  }
469  else if( token == T_inner )
470  {
471  retVal = LSET::InternalCuMask();
472  }
473  else
474  {
475  wxString layerName = FromUTF8();
476  wxPGChoices& layerMap = ENUM_MAP<PCB_LAYER_ID>::Instance().Choices();
477 
478  for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii )
479  {
480  wxPGChoiceEntry& entry = layerMap[ii];
481 
482  if( entry.GetText().Matches( layerName ) )
483  retVal.set( ToLAYER_ID( entry.GetValue() ) );
484  }
485 
486  if( !retVal.any() )
487  {
488  reportError( wxString::Format( _( "Unrecognized layer '%s'." ),
489  layerName ) );
490  retVal.set( Rescue );
491  }
492  }
493 
494  if( (int) NextTok() != DSN_RIGHT )
495  {
496  reportError( wxString::Format( _( "Unrecognized item '%s'." ), FromUTF8() ) );
497  parseUnknown();
498  }
499 
500  return retVal;
501 }
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:750
void reportError(const wxString &aMessage)
REPORTER * m_reporter
virtual bool HasMessage() const =0
Returns true if the reporter client is non-empty.
void SetErrorCallback(std::function< void(const wxString &aMessage, int aOffset)> aCallback)
DRC_RULE_CONDITION * m_Condition
Definition: drc_rule.h:99
void SetMin(T v)
Definition: minoptmax.h:41
wxString m_Name
Definition: drc_rule.h:96
void SetOpt(T v)
Definition: minoptmax.h:43
static ENUM_MAP< T > & Instance()
Definition: property.h:510
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:64
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
#define DRC_RULE_FILE_VERSION
MINOPTMAX< int > m_Value
Definition: drc_rule.h:143
DRC_RULES_PARSER(const wxString &aSource, const wxString &aSourceDescr)
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:502
#define THROW_PARSE_ERROR(aProblem, aSource, aInputLine, aLineNumber, aByteIndex)
Definition: ki_exception.h:164
void parseValueWithUnits(const wxString &aExpr, int &aResult)
DRC_RULE * parseDRC_RULE()
void Parse(std::vector< DRC_RULE * > &aRules, REPORTER *aReporter)
void SetMax(T v)
Definition: minoptmax.h:42
static LSET InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition: lset.cpp:710
static LSET ExternalCuMask()
Return a mask holding the Front and Bottom layers.
Definition: lset.cpp:780
#define _(s)
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
wxString m_LayerSource
Definition: drc_rule.h:97
void parseConstraint(DRC_RULE *aRule)
LSET m_LayerCondition
Definition: drc_rule.h:98
void AddConstraint(DRC_CONSTRAINT &aConstraint)
Definition: drc_rule.cpp:46
int m_DisallowFlags
Definition: drc_rule.h:144
bool Evaluate(const wxString &aExpr)
bool Compile(REPORTER *aReporter, int aSourceLine=0, int aSourceOffset=0)
DRC_CONSTRAINT_T m_Type
Definition: drc_rule.h:142
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition: lset.cpp:905