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 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" ),
61  CurLineNumber(),
62  CurOffset(),
63  first,
64  rest );
65 
67  }
68  else
69  {
70  wxString msg = wxString::Format( _( "ERROR: %s%s" ),
71  first,
72  rest );
73 
74  THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
75  }
76 }
77 
78 
80 {
81  int depth = 1;
82 
83  for( T token = NextTok(); token != T_EOF; token = NextTok() )
84  {
85  if( token == T_LEFT )
86  depth++;
87 
88  if( token == T_RIGHT )
89  {
90  if( --depth == 0 )
91  break;
92  }
93  }
94 }
95 
96 
97 void DRC_RULES_PARSER::Parse( std::vector<DRC_RULE*>& aRules, REPORTER* aReporter )
98 {
99  bool haveVersion = false;
100  wxString msg;
101 
102  m_reporter = aReporter;
103 
104  for( T token = NextTok(); token != T_EOF; token = NextTok() )
105  {
106  if( token != T_LEFT )
107  reportError( _( "Missing '('." ) );
108 
109  token = NextTok();
110 
111  if( !haveVersion && token != T_version )
112  {
113  reportError( _( "Missing version statement." ) );
114  haveVersion = true; // don't keep on reporting it
115  }
116 
117  switch( token )
118  {
119  case T_version:
120  haveVersion = true;
121  token = NextTok();
122 
123  if( (int) token == DSN_RIGHT )
124  {
125  reportError( _( "Missing version number." ) );
126  break;
127  }
128 
129  if( (int) token == DSN_NUMBER )
130  {
131  m_requiredVersion = (int)strtol( CurText(), NULL, 10 );
133  token = NextTok();
134  }
135  else
136  {
137  msg.Printf( _( "Unrecognized item '%s'.| Expected version number" ),
138  FromUTF8() );
139  reportError( msg );
140  }
141 
142  if( (int) token != DSN_RIGHT )
143  {
144  msg.Printf( _( "Unrecognized item '%s'." ),
145  FromUTF8() );
146  reportError( msg );
147  parseUnknown();
148  }
149 
150  break;
151 
152  case T_rule:
153  aRules.push_back( parseDRC_RULE() );
154  break;
155 
156  case T_EOF:
157  reportError( _( "Incomplete statement." ) );
158  break;
159 
160  default:
161  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ),
162  FromUTF8(),
163  "'rule', 'version'" );
164  reportError( msg );
165  parseUnknown();
166  }
167  }
168 
169  if( m_reporter && !m_reporter->HasMessage() )
170  m_reporter->Report( _( "No errors found." ), RPT_SEVERITY_INFO );
171 
172  m_reporter = nullptr;
173 }
174 
175 
177 {
178  DRC_RULE* rule = new DRC_RULE();
179  T token = NextTok();
180  wxString msg;
181 
182  if( !IsSymbol( token ) )
183  reportError( _( "Missing rule name." ) );
184 
185  rule->m_Name = FromUTF8();
186 
187  for( token = NextTok(); token != T_RIGHT && token != T_EOF; token = NextTok() )
188  {
189  if( token != T_LEFT )
190  reportError( _( "Missing '('." ) );
191 
192  token = NextTok();
193 
194  switch( token )
195  {
196  case T_constraint:
197  parseConstraint( rule );
198  break;
199 
200  case T_condition:
201  token = NextTok();
202 
203  if( (int) token == DSN_RIGHT )
204  {
205  reportError( _( "Missing condition expression." ) );
206  break;
207  }
208 
209  if( IsSymbol( token ) )
210  {
211  rule->m_Condition = new DRC_RULE_CONDITION( FromUTF8() );
212  rule->m_Condition->Compile( m_reporter, CurLineNumber(), CurOffset() );
213  }
214  else
215  {
216  msg.Printf( _( "Unrecognized item '%s'.| Expected quoted expression." ),
217  FromUTF8() );
218  reportError( msg );
219  }
220 
221  if( (int) NextTok() != DSN_RIGHT )
222  {
223  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
224  FromUTF8() ) );
225  parseUnknown();
226  }
227 
228  break;
229 
230  case T_layer:
231  rule->m_LayerSource = FromUTF8();
232  rule->m_LayerCondition = parseLayer();
233  break;
234 
235  case T_EOF:
236  reportError( _( "Incomplete statement." ) );
237  return rule;
238 
239  default:
240  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ),
241  FromUTF8(),
242  "'constraint', 'condition', 'disallow'" );
243  reportError( msg );
244  parseUnknown();
245  }
246  }
247 
248  if( (int) CurTok() != DSN_RIGHT )
249  reportError( _( "Missing ')'." ) );
250 
251  return rule;
252 }
253 
254 
256 {
257  DRC_CONSTRAINT constraint;
258 
259  int value;
260  wxString msg;
261 
262  T token = NextTok();
263 
264  if( (int) token == DSN_RIGHT || token == T_EOF )
265  {
266  msg.Printf( _( "Missing constraint type.| Expected %s." ),
267  "'clearance', 'hole_clearance', 'edge_clearance', 'hole', 'hole_to_hole', "
268  "'courtyard_clearance', 'silk_clearance', 'track_width', 'annular_width', "
269  "'disallow', 'length', 'skew', 'via_count', 'diff_pair_gap' or "
270  "'diff_pair_uncoupled'" );
271  reportError( msg );
272  return;
273  }
274 
275  switch( token )
276  {
277  case T_clearance: constraint.m_Type = CLEARANCE_CONSTRAINT; break;
278  case T_hole_clearance: constraint.m_Type = HOLE_CLEARANCE_CONSTRAINT; break;
279  case T_edge_clearance: constraint.m_Type = EDGE_CLEARANCE_CONSTRAINT; break;
280  case T_hole: constraint.m_Type = HOLE_SIZE_CONSTRAINT; break;
281  case T_hole_to_hole: constraint.m_Type = HOLE_TO_HOLE_CONSTRAINT; break;
282  case T_courtyard_clearance: constraint.m_Type = COURTYARD_CLEARANCE_CONSTRAINT; break;
283  case T_silk_clearance: constraint.m_Type = SILK_CLEARANCE_CONSTRAINT; break;
284  case T_track_width: constraint.m_Type = TRACK_WIDTH_CONSTRAINT; break;
285  case T_annular_width: constraint.m_Type = ANNULAR_WIDTH_CONSTRAINT; break;
286  case T_disallow: constraint.m_Type = DISALLOW_CONSTRAINT; break;
287  case T_length: constraint.m_Type = LENGTH_CONSTRAINT; break;
288  case T_skew: constraint.m_Type = SKEW_CONSTRAINT; break;
289  case T_via_count: constraint.m_Type = VIA_COUNT_CONSTRAINT; break;
290  case T_diff_pair_gap: constraint.m_Type = DIFF_PAIR_GAP_CONSTRAINT; break;
291  case T_diff_pair_uncoupled: constraint.m_Type = DIFF_PAIR_MAX_UNCOUPLED_CONSTRAINT; break;
292  default:
293  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ),
294  FromUTF8(),
295  "'clearance', 'hole_clearance', 'edge_clearance', 'hole', hole_to_hole',"
296  "'courtyard_clearance', 'silk_clearance', 'track_width', 'annular_width', "
297  "'disallow', 'length', 'skew', 'diff_pair_gap' or 'diff_pair_uncoupled'." );
298  reportError( msg );
299  }
300 
301  if( constraint.m_Type == DISALLOW_CONSTRAINT )
302  {
303  for( token = NextTok(); token != T_RIGHT; token = NextTok() )
304  {
305  if( (int) token == DSN_STRING )
306  token = GetCurStrAsToken();
307 
308  switch( token )
309  {
310  case T_track: constraint.m_DisallowFlags |= DRC_DISALLOW_TRACKS; break;
311  case T_via: constraint.m_DisallowFlags |= DRC_DISALLOW_VIAS; break;
312  case T_micro_via: constraint.m_DisallowFlags |= DRC_DISALLOW_MICRO_VIAS; break;
313  case T_buried_via: constraint.m_DisallowFlags |= DRC_DISALLOW_BB_VIAS; break;
314  case T_pad: constraint.m_DisallowFlags |= DRC_DISALLOW_PADS; break;
315  case T_zone: constraint.m_DisallowFlags |= DRC_DISALLOW_ZONES; break;
316  case T_text: constraint.m_DisallowFlags |= DRC_DISALLOW_TEXTS; break;
317  case T_graphic: constraint.m_DisallowFlags |= DRC_DISALLOW_GRAPHICS; break;
318  case T_hole: constraint.m_DisallowFlags |= DRC_DISALLOW_HOLES; break;
319  case T_footprint: constraint.m_DisallowFlags |= DRC_DISALLOW_FOOTPRINTS; break;
320 
321  case T_EOF:
322  reportError( _( "Missing ')'." ) );
323  return;
324 
325  default:
326  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ),
327  FromUTF8(),
328  "'track', 'via', 'micro_via', 'buried_via', 'pad', 'zone', 'text', "
329  "'graphic', 'hole' or 'footprint'." );
330  reportError( msg );
331  break;
332  }
333  }
334 
335  if( (int) CurTok() != DSN_RIGHT )
336  reportError( _( "Missing ')'." ) );
337 
338  aRule->AddConstraint( constraint );
339  return;
340  }
341 
342  for( token = NextTok(); token != T_RIGHT && token != T_EOF; token = NextTok() )
343  {
344  if( token != T_LEFT )
345  reportError( _( "Missing '('." ) );
346 
347  token = NextTok();
348 
349  switch( token )
350  {
351  case T_min:
352  token = NextTok();
353 
354  if( (int) token == DSN_RIGHT )
355  {
356  reportError( _( "Missing min value." ) );
357  break;
358  }
359 
360  parseValueWithUnits( FromUTF8(), value );
361  constraint.m_Value.SetMin( value );
362 
363  if( (int) NextTok() != DSN_RIGHT )
364  {
365  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
366  FromUTF8() ) );
367  parseUnknown();
368  }
369 
370  break;
371 
372  case T_max:
373  token = NextTok();
374 
375  if( (int) token == DSN_RIGHT )
376  {
377  reportError( _( "Missing max value." ) );
378  break;
379  }
380 
381  parseValueWithUnits( FromUTF8(), value );
382  constraint.m_Value.SetMax( value );
383 
384  if( (int) NextTok() != DSN_RIGHT )
385  {
386  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
387  FromUTF8() ) );
388  parseUnknown();
389  }
390 
391  break;
392 
393  case T_opt:
394  token = NextTok();
395 
396  if( (int) token == DSN_RIGHT )
397  {
398  reportError( _( "Missing opt value." ) );
399  break;
400  }
401 
402  parseValueWithUnits( FromUTF8(), value );
403  constraint.m_Value.SetOpt( value );
404 
405  if( (int) NextTok() != DSN_RIGHT )
406  {
407  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
408  FromUTF8() ) );
409  parseUnknown();
410  }
411 
412  break;
413 
414  case T_EOF:
415  reportError( _( "Incomplete statement." ) );
416  return;
417 
418  default:
419  msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ),
420  FromUTF8(),
421  "'min', 'max', 'opt'" );
422  reportError( msg );
423  parseUnknown();
424  }
425  }
426 
427  if( (int) CurTok() != DSN_RIGHT )
428  reportError( _( "Missing ')'." ) );
429 
430  aRule->AddConstraint( constraint );
431 }
432 
433 
434 void DRC_RULES_PARSER::parseValueWithUnits( const wxString& aExpr, int& aResult )
435 {
436  auto errorHandler = [&]( const wxString& aMessage, int aOffset )
437  {
438  wxString rest;
439  wxString first = aMessage.BeforeFirst( '|', &rest );
440 
441  if( m_reporter )
442  {
443  wxString msg = wxString::Format( _( "ERROR: <a href='%d:%d'>%s</a>%s" ),
444  CurLineNumber(),
445  CurOffset() + aOffset,
446  first,
447  rest );
448 
450  }
451  else
452  {
453  wxString msg = wxString::Format( _( "ERROR: %s%s" ),
454  first,
455  rest );
456 
457  THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(),
458  CurOffset() + aOffset );
459  }
460  };
461 
462  PCB_EXPR_EVALUATOR evaluator;
463  evaluator.SetErrorCallback( errorHandler );
464 
465  evaluator.Evaluate( aExpr );
466  aResult = evaluator.Result();
467 }
468 
469 
471 {
472  LSET retVal;
473  int token = NextTok();
474 
475  if( (int) token == DSN_RIGHT )
476  {
477  reportError( _( "Missing layer name or type." ) );
478  return LSET::AllCuMask();
479  }
480  else if( token == T_outer )
481  {
482  retVal = LSET::ExternalCuMask();
483  }
484  else if( token == T_inner )
485  {
486  retVal = LSET::InternalCuMask();
487  }
488  else
489  {
490  wxString layerName = FromUTF8();
491  wxPGChoices& layerMap = ENUM_MAP<PCB_LAYER_ID>::Instance().Choices();
492 
493  for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii )
494  {
495  wxPGChoiceEntry& entry = layerMap[ii];
496 
497  if( entry.GetText().Matches( layerName ) )
498  retVal.set( ToLAYER_ID( entry.GetValue() ) );
499  }
500 
501  if( !retVal.any() )
502  {
503  reportError( wxString::Format( _( "Unrecognized layer '%s'." ),
504  layerName ) );
505  retVal.set( Rescue );
506  }
507  }
508 
509  if( (int) NextTok() != DSN_RIGHT )
510  {
511  reportError( wxString::Format( _( "Unrecognized item '%s'." ),
512  FromUTF8() ) );
513  parseUnknown();
514  }
515 
516  return retVal;
517 }
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:508
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:142
DRC_RULES_PARSER(const wxString &aSource, const wxString &aSourceDescr)
LSET is a set of PCB_LAYER_IDs.
#define THROW_PARSE_ERROR(aProblem, aSource, aInputLine, aLineNumber, aByteIndex)
Definition: ki_exception.h:164
#define NULL
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
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
#define _(s)
Definition: 3d_actions.cpp:33
void AddConstraint(DRC_CONSTRAINT &aConstraint)
Definition: drc_rule.cpp:46
int m_DisallowFlags
Definition: drc_rule.h:143
bool Evaluate(const wxString &aExpr)
bool Compile(REPORTER *aReporter, int aSourceLine=0, int aSourceOffset=0)
DRC_CONSTRAINT_T m_Type
Definition: drc_rule.h:141
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition: lset.cpp:905