20#ifndef DRC_RULES_DOWNGRADE_H
21#define DRC_RULES_DOWNGRADE_H
45 if( aTarget.
m_id == vocabulary.m_targetId )
61inline bool DrcVocabularyHas(
const char*
const* aWords,
size_t aCount,
const std::string& aWord )
63 return std::binary_search( aWords, aWords + aCount, aWord );
97inline bool TokenizeDrcRules(
const std::string& aText, std::vector<DRC_RULE_TOKEN>& aTokens, wxString& aError )
99 std::vector<size_t> lineStarts{ 0 };
101 for(
size_t i = 0; i < aText.size(); i++ )
103 if( aText[i] ==
'\n' )
104 lineStarts.push_back( i + 1 );
107 DSNLEXER lexer( aText, wxT(
"drc_rules_downgrade" ) );
120 aTokens.push_back( { type, lexer.
CurText(), offset } );
135inline bool SplitDrcRuleBlocks(
const std::vector<DRC_RULE_TOKEN>& aTokens, std::vector<DRC_RULE_BLOCK>& aBlocks,
140 for(
size_t i = 0; i < aTokens.size(); i++ )
146 aError =
_(
"Design rule comments must start on a separate line." );
157 aError =
_(
"Design rules contain text outside a rule." );
165 auto nextCode = [&aTokens](
size_t aFrom )
167 while( aFrom < aTokens.size() && aTokens[aFrom].m_type ==
DSN_COMMENT )
173 size_t head = nextCode( i + 1 );
175 if( head < aTokens.size() && aTokens[head].m_type ==
DSN_SYMBOL )
176 block.
m_head = aTokens[head].m_text;
178 size_t name = nextCode( head + 1 );
183 block.
m_name = wxString::FromUTF8( aTokens[
name].m_text.c_str() );
186 aBlocks.push_back( block );
199 aBlocks.back().m_end = token.
m_offset + 1;
200 aBlocks.back().m_last = i;
207 aError =
_(
"Unbalanced parentheses in design rules." );
219 std::vector<std::string> nodes;
220 std::vector<std::string> words;
221 std::string expressions;
223 bool headPending =
false;
224 bool kindPending =
false;
225 bool namePending =
false;
228 auto addExpression = [&expressions](
const std::string& aExpression )
232 for(
size_t j = 0; j < aExpression.size(); j++ )
234 char c = aExpression[j];
238 if( c ==
'\\' && j + 1 < aExpression.size() )
240 else if( c == quote )
243 else if( c ==
'\'' || c ==
'"' )
266 nodes.emplace_back();
281 if( token.
m_text.find(
"${" ) != std::string::npos )
287 std::string folded = token.
m_text;
289 for(
char& c : folded )
290 c =
static_cast<char>( std::tolower(
static_cast<unsigned char>( c ) ) );
293 if( folded.find(
"${class:" ) != std::string::npos )
304 nodes.back() = token.
m_text;
305 words.push_back( token.
m_text );
306 kindPending = token.
m_text ==
"constraint";
307 namePending = token.
m_text ==
"rule";
311 const std::string& node = nodes.back();
317 else if( kindPending )
321 words.push_back( token.
m_text );
323 if( token.
m_text ==
"assertion" || token.
m_text ==
"disallow" || token.
m_text ==
"zone_connection" )
324 nodes.back() = token.
m_text;
326 else if( node ==
"condition" || node ==
"assertion" )
328 addExpression( token.
m_text );
330 else if( node ==
"min" || node ==
"max" || node ==
"opt" )
332 values += token.
m_text +
' ';
334 else if( node ==
"layer" )
338 else if( node ==
"disallow" || node ==
"zone_connection" )
341 words.push_back( token.
m_text );
345 words.push_back( token.
m_text );
349 for(
const std::string& word : words )
352 if( word.find(
'.' ) != std::string::npos )
359 auto isWord = [](
char c )
361 return std::isalnum(
static_cast<unsigned char>( c ) ) || c ==
'_';
364 std::string folded = expressions;
366 for(
char& c : folded )
367 c =
static_cast<char>( std::tolower(
static_cast<unsigned char>( c ) ) );
371 for(
size_t i = 0; i < folded.size(); i++ )
373 if( !std::isalpha(
static_cast<unsigned char>( folded[i] ) ) && folded[i] !=
'_' )
376 if( i > 0 && isWord( folded[i - 1] ) )
381 while(
end < folded.size() && isWord( folded[
end] ) )
384 const std::string word = folded.substr( i,
end - i );
388 while(
next < folded.size() && std::isspace(
static_cast<unsigned char>( folded[
next] ) ) )
393 while( prev > 0 && std::isspace(
static_cast<unsigned char>( folded[prev - 1] ) ) )
396 if(
next < folded.size() && folded[
next] ==
'(' )
407 else if( prev > 0 && folded[prev - 1] ==
'.' )
410 if( word ==
"parent" &&
next < folded.size() && folded[
next] ==
'.' )
422 const std::string numeric = values +
'\n' + expressions;
424 for(
size_t i = 0; i < numeric.size(); i++ )
426 if( !std::isalpha(
static_cast<unsigned char>( numeric[i] ) ) )
429 if( i > 0 && ( std::isalpha(
static_cast<unsigned char>( numeric[i - 1] ) ) || numeric[i - 1] ==
'_' ) )
434 while( before > 0 && std::isspace(
static_cast<unsigned char>( numeric[before - 1] ) ) )
439 while(
end < numeric.size() && isWord( numeric[
end] ) )
442 if( before > 0 && std::isdigit(
static_cast<unsigned char>( numeric[before - 1] ) )
458 std::string out =
"# ";
460 for(
char c : aBlock )
476 std::string
text = aRulesText.ToStdString( wxConvUTF8 );
484 result.m_error = wxString::Format(
_(
"No design rule vocabulary is recorded for %s." ), aTarget.
m_name );
488 std::vector<DRC_RULE_TOKEN> tokens;
489 std::vector<DRC_RULE_BLOCK> blocks;
494 const wxString header = wxT(
"(version 1)" );
495 const wxString eol =
text.find(
"\r\n" ) != std::string::npos ? wxT(
"\r\n" ) : wxT(
"\n" );
498 bool headerWritten =
false;
499 bool commentedOut =
false;
501 auto endsOnBlankLine = [](
const wxString& aText )
503 int lineStart = aText.Find(
'\n',
true );
504 return aText.Mid( lineStart == wxNOT_FOUND ? 0 : lineStart + 1 ).Trim().IsEmpty();
510 wxString prefix = wxString::FromUTF8(
text.substr( consumed, block.m_start - consumed ).c_str() );
511 std::string body =
text.substr( block.m_start, block.m_end - block.m_start );
512 consumed = block.m_end;
515 if( block.m_head ==
"version" )
522 headerWritten =
true;
528 result.m_text += header + eol;
529 headerWritten =
true;
538 if( ( unsupported || commentedOut ) && !endsOnBlankLine(
result.m_text ) )
543 result.m_dropped.push_back( block.m_name );
544 result.m_text += wxT(
"# Not supported by " ) + aTarget.
m_name + wxT(
":" ) + eol;
549 result.m_text += wxString::FromUTF8( body.c_str() );
552 commentedOut = unsupported;
556 result.m_text += header + eol;
559 result.m_text += wxString::FromUTF8(
text.substr( consumed ).c_str() );
561 if( !
result.m_text.EndsWith( wxT(
"\n" ) ) )
Implement a lexical analyzer for the SPECCTRA DSN file format.
const char * CurText() const
Return a pointer to the current token's text.
int NextTok()
Return the next token found in the input file or DSN_EOF when reaching the end of file.
bool SetCommentsAreTokens(bool val)
Change the handling of comments.
int CurOffset() const
Return the byte offset within the current line, using a 1 based index.
int CurLineNumber() const
Return the current line number within my LINE_READER.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString Problem() const
what was the problem?
std::string CommentOutDrcBlock(const std::string &aBlock)
Prefix every line, so the target reads the whole block as comments.
bool DrcVocabularyHas(const char *const *aWords, size_t aCount, const std::string &aWord)
The generated arrays are sorted, so membership is a binary search.
constexpr const char * DRC_RULE_SEMANTIC_BREAKS[]
A function a release parses but answers differently, which the vocabulary cannot say.
bool DrcRuleUnsupportedByTarget(const std::vector< DRC_RULE_TOKEN > &aTokens, const DRC_RULE_BLOCK &aBlock, const DRC_RULE_VOCABULARY &aVocabulary)
True if the rule uses something the target's parser or evaluator cannot handle.
bool SplitDrcRuleBlocks(const std::vector< DRC_RULE_TOKEN > &aTokens, std::vector< DRC_RULE_BLOCK > &aBlocks, wxString &aError)
Group the tokens into top-level blocks.
const DRC_RULE_VOCABULARY * FindDrcRuleVocabulary(const DOWNGRADE_TARGET &aTarget)
The vocabulary a target release could parse, generated from its release tags by tools/generate_drc_ru...
bool TokenizeDrcRules(const std::string &aText, std::vector< DRC_RULE_TOKEN > &aTokens, wxString &aError)
Tokenize with the lexer the rule readers use, so comments, strings and symbols end exactly where they...
DRC_RULES_FILTER_RESULT FilterDrcRulesForTarget(const wxString &aRulesText, const DOWNGRADE_TARGET &aTarget)
Filter a .kicad_dru file for the target.
constexpr DRC_RULE_VOCABULARY DRC_RULE_VOCABULARIES[]
An older KiCad release we can export a project back to.
wxString m_id
Stable machine key for CLI and jobsets, e.g. "10.0".
wxString m_name
User-facing name, e.g. "KiCad 10.0".
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.
A top-level block of the rules file, as byte span and token range.
wxString m_name
The name after a "rule" head, for the report.
size_t m_start
Byte offset of the opening parenthesis.
size_t m_last
Index of the closing parenthesis token.
size_t m_first
Index of the opening parenthesis token.
size_t m_end
Byte offset just past the closing parenthesis.
std::string m_head
The word after the opening parenthesis, "rule" or "version".
std::string m_text
The token as the lexer reports it, strings without their quotes.
size_t m_offset
Byte offset of the token's first character in the source.
int m_type
A DSNLEXER token type.
Everything a rules file could name in one release.
const char *const * m_tokens
const char *const * m_properties
const char *const * m_units
const char *const * m_functions
wxString result
Test unit parsing edge cases and error handling.