KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_parser_tool.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 The KiCad Developers, see AUTHORS.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, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <chrono>
23#include <fstream>
24#include <iostream>
25#include <string>
26
27#include <wx/cmdline.h>
28#include <wx/msgout.h>
29
30#include <fmt/format.h>
31
32#include <board.h>
33#include <board_item.h>
34#include <common.h>
35#include <core/profile.h>
36
39
41
42
43using PARSE_DURATION = std::chrono::microseconds;
44
45
50{
51public:
52 virtual ~BOARD_PARSER() = default;
53
59 virtual std::unique_ptr<BOARD_ITEM> Parse() = 0;
60};
61
62
67{
68public:
69 FILE_PARSER( PCB_IO_MGR::PCB_FILE_T aFileType, const wxString& aFileName ) :
70 m_fileType( aFileType ),
71 m_fileName( aFileName )
72 {
73 }
74
75 std::unique_ptr<BOARD_ITEM> Parse() override
76 {
77 BOARD* board = PCB_IO_MGR::Load( m_fileType, m_fileName, nullptr, {}, nullptr, nullptr );
78 return std::unique_ptr<BOARD_ITEM>( board );
79 }
80
81private:
83 wxString m_fileName;
84};
85
86
93{
94public:
102 virtual void PrepareStream( std::istream& aStream ) = 0;
103};
104
105
107{
108public:
109 void PrepareStream( std::istream& aStream ) override { m_reader.SetStream( aStream ); }
110
111 std::unique_ptr<BOARD_ITEM> Parse() override
112 {
113 PCB_IO_KICAD_SEXPR_PARSER parser( &m_reader, nullptr, nullptr );
114 return std::unique_ptr<BOARD_ITEM>{ parser.Parse() };
115 }
116
117private:
119};
120
121
123{
124public:
125 void PrepareStream( std::istream& aStream ) override
126 {
127 // Allegro parser expects to mmap a file, so we need to
128 // dump it all in memory to simulate that.
129 m_buffer.assign( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>() );
130
131 // Check if stream reading failed
132 if( aStream.fail() && !aStream.eof() )
133 {
134 THROW_IO_ERROR( _( "Failed to read from input stream" ) );
135 }
136 }
137
138 std::unique_ptr<BOARD_ITEM> Parse() override
139 {
140 PCB_IO_ALLEGRO allegroParser;
141 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
142
143 if( !allegroParser.LoadBoardFromData( m_buffer.data(), m_buffer.size(), *board ) )
144 {
145 return nullptr;
146 }
147
148 return board;
149 }
150
151private:
152 std::vector<uint8_t> m_buffer;
153};
154
155
160{
161public:
162 PCB_PARSE_RUNNER( PCB_IO_MGR::PCB_FILE_T aPluginType, bool aVerbose ) :
163 m_pluginType( aPluginType ),
164 m_verbose( aVerbose )
165 {
166 }
167
168 bool Parse( std::istream& aStream )
169 {
170 std::unique_ptr<STREAM_PARSER> parser;
171
172 switch( m_pluginType )
173 {
174 case PCB_IO_MGR::KICAD_SEXP: parser = std::make_unique<SEXPR_STREAM_PARSER>(); break;
175 case PCB_IO_MGR::ALLEGRO: parser = std::make_unique<ALLEGRO_BRD_STREAM_PARSER>(); break;
176 default:
177 std::cerr << fmt::format( "Unsupported plugin type for streaming input: {}",
178 static_cast<int>( m_pluginType ) )
179 << std::endl;
180 return false;
181 }
182
183 wxCHECK( parser, false );
184
185 try
186 {
187 parser->PrepareStream( aStream );
188 }
189 catch( const IO_ERROR& e )
190 {
191 std::cerr << fmt::format( "Error preparing stream: {}", e.What().ToStdString() ) << std::endl;
192 return false;
193 }
194
195 return doParse( *parser );
196 }
197
198 bool Parse( const wxString& aFilename )
199 {
200 FILE_PARSER parser( m_pluginType, aFilename );
201 return doParse( parser );
202 }
203
204private:
205 bool doParse( BOARD_PARSER& aParser )
206 {
207 std::unique_ptr<BOARD_ITEM> board;
208 PARSE_DURATION duration{};
209
210 try
211 {
212 PROF_TIMER timer;
213 board = aParser.Parse();
214 duration = timer.SinceStart<PARSE_DURATION>();
215 }
216 catch( const IO_ERROR& e )
217 {
218 std::cerr << "Parsing failed: " << e.What() << std::endl;
219 }
220
221 if( m_verbose )
222 {
223 std::cout << fmt::format( "Took: {}us", duration.count() ) << std::endl;
224
225 if( board )
226 std::cout << fmt::format( " {} nets", board->GetBoard()->GetNetCount() ) << std::endl;
227 }
228
229 return board != nullptr;
230 }
231
234};
235
236
237static const wxCmdLineEntryDesc g_cmdLineDesc[] = {
238 {
239 wxCMD_LINE_SWITCH,
240 "h",
241 "help",
242 _( "displays help on the command line parameters" ).mb_str(),
243 wxCMD_LINE_VAL_NONE,
244 wxCMD_LINE_OPTION_HELP,
245 },
246 {
247 wxCMD_LINE_SWITCH,
248 "v",
249 "verbose",
250 _( "print parsing information" ).mb_str(),
251 },
252 {
253 wxCMD_LINE_OPTION,
254 "p",
255 "plugin",
256 _( "parser plugin to use (kicad, allegro, etc.)" ).mb_str(),
257 wxCMD_LINE_VAL_STRING,
258 },
259 {
260 wxCMD_LINE_SWITCH,
261 nullptr,
262 "list-plugins",
263 _( "list available plugins and exit" ).mb_str(),
264 },
265 {
266 wxCMD_LINE_OPTION,
267 "l",
268 "loop",
269 _( "number of times to loop when parsing from stdin (for AFL)" ).mb_str(),
270 wxCMD_LINE_VAL_NUMBER,
271 },
272 {
273 wxCMD_LINE_PARAM,
274 nullptr,
275 nullptr,
276 _( "input file" ).mb_str(),
277 wxCMD_LINE_VAL_STRING,
278 wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE,
279 },
280 {
281 wxCMD_LINE_NONE,
282 }
283};
284
285
290
291
295static const std::map<std::string, PCB_IO_MGR::PCB_FILE_T> pluginTypeMap = {
296 { "kicad", PCB_IO_MGR::KICAD_SEXP },
297 { "legacy", PCB_IO_MGR::LEGACY },
298 { "allegro", PCB_IO_MGR::ALLEGRO },
299 { "altium", PCB_IO_MGR::ALTIUM_DESIGNER },
300 { "cadstar", PCB_IO_MGR::CADSTAR_PCB_ARCHIVE },
301 { "eagle", PCB_IO_MGR::EAGLE },
302 { "easyeda", PCB_IO_MGR::EASYEDA },
303 { "easyedapro", PCB_IO_MGR::EASYEDAPRO },
304 { "fabmaster", PCB_IO_MGR::FABMASTER },
305 { "geda", PCB_IO_MGR::GEDA_PCB },
306 { "pads", PCB_IO_MGR::PADS },
307 { "pcad", PCB_IO_MGR::PCAD },
308 { "solidworks", PCB_IO_MGR::SOLIDWORKS_PCB },
309 // { "ipc2581", PCB_IO_MGR::IPC2581 }, // readers
310 // { "odbpp", PCB_IO_MGR::ODBPP },
311};
312
313
314static PCB_IO_MGR::PCB_FILE_T FindPluginTypeFromParams( const wxString& aExplicitPlugin, const wxString& aPath )
315{
316 if( aExplicitPlugin == "auto" )
317 {
318 // Try to guess the plugin type from the first file
320 }
321
322 auto pluginIt = pluginTypeMap.find( aExplicitPlugin.ToStdString() );
323 if( pluginIt == pluginTypeMap.end() )
324 {
326 }
327 return pluginIt->second;
328}
329
330
331int pcb_parser_main_func( int argc, char** argv )
332{
333#ifdef __AFL_COMPILER
334 __AFL_INIT();
335#endif
336
337 wxMessageOutput::Set( new wxMessageOutputStderr );
338 wxCmdLineParser cl_parser( argc, argv );
339 cl_parser.SetDesc( g_cmdLineDesc );
340 cl_parser.AddUsageText( _( "This program parses PCB files, either from the stdin stream or "
341 "from the given filenames. This can be used either for standalone "
342 "testing of the parser or for fuzz testing." ) );
343
344 int cmd_parsed_ok = cl_parser.Parse();
345 if( cmd_parsed_ok != 0 )
346 {
347 // Help and invalid input both stop here
348 return ( cmd_parsed_ok == -1 ) ? KI_TEST::RET_CODES::OK : KI_TEST::RET_CODES::BAD_CMDLINE;
349 }
350
351 const bool verbose = cl_parser.Found( "verbose" );
352
353 if( cl_parser.Found( "list-plugins" ) )
354 {
355 for( const auto& [name, type] : pluginTypeMap )
356 {
357 std::cout << name << std::endl;
358 }
359 std::cout << "auto" << std::endl;
360
362 }
363
364 bool ok = true;
365 const size_t file_count = cl_parser.GetParamCount();
366
367 wxString plugin( "auto" );
368 cl_parser.Found( "plugin", &plugin );
369
370 long aflLoopCount = 1;
371 cl_parser.Found( "loop", &aflLoopCount );
372
373 if( file_count == 0 && plugin == "auto" )
374 {
375 std::cerr << "When parsing from stdin, you must specify the plugin type with -p" << std::endl;
377 }
378
379 const PCB_IO_MGR::PCB_FILE_T pluginType =
380 FindPluginTypeFromParams( plugin, file_count > 0 ? cl_parser.GetParam( 0 ) : wxString( "" ) );
381
382 if( pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
383 {
384 std::cerr << fmt::format( "Failed to determine plugin type for input using plugin {}", plugin.ToStdString() )
385 << std::endl;
387 }
388
389 if( verbose )
390 {
391 std::cout << "Using plugin type: " << PCB_IO_MGR::ShowType( pluginType ) << std::endl;
392 }
393
394 PCB_PARSE_RUNNER runner( pluginType, verbose );
395
396 std::vector<std::string> failedFiles;
397
398 if( file_count == 0 )
399 {
400 // Parse the file provided on stdin - used by AFL to drive the
401 // program
402#ifdef __AFL_COMPILER
403 while( __AFL_LOOP( aflLoopCount ) )
404#endif
405 {
406 ok = runner.Parse( std::cin );
407 }
408 }
409 else
410 {
411 // Parse 'n' files given on the command line
412 // (this is useful for input minimisation (e.g. afl-tmin) as
413 // well as manual testing
414 for( size_t i = 0; i < file_count; i++ )
415 {
416 const wxString filename = cl_parser.GetParam( i );
417
418 if( verbose )
419 std::cout << fmt::format( "Parsing: {}", filename.ToStdString() ) << std::endl;
420
421 if( !runner.Parse( filename ) )
422 {
423 ok = false;
424 failedFiles.push_back( filename.ToStdString() );
425 }
426 }
427 }
428
429 for( const auto& failedFile : failedFiles )
430 {
431 std::cerr << fmt::format( "Failed to parse: {}", failedFile ) << std::endl;
432 }
433
434 if( !ok )
436
438}
439
440
441static bool registered = UTILITY_REGISTRY::Register( { "pcb_parser",
442 "Parse a PCB file",
const char * name
std::vector< uint8_t > m_buffer
std::unique_ptr< BOARD_ITEM > Parse() override
Actually perform the parsing and return a BOARD_ITEM if successful, or nullptr if not.
void PrepareStream(std::istream &aStream) override
Take some input stream and prepare it for parsing.
Generic board parser - this makes no assumption about what the source data might be.
virtual std::unique_ptr< BOARD_ITEM > Parse()=0
Actually perform the parsing and return a BOARD_ITEM if successful, or nullptr if not.
virtual ~BOARD_PARSER()=default
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
Provide the BOARD_PARSER interface wrapping a normal PCB_IO file-based plugin lookup.
FILE_PARSER(PCB_IO_MGR::PCB_FILE_T aFileType, const wxString &aFileName)
wxString m_fileName
PCB_IO_MGR::PCB_FILE_T m_fileType
std::unique_ptr< BOARD_ITEM > Parse() override
Actually perform the parsing and return a BOARD_ITEM if successful, or nullptr if not.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
bool LoadBoardFromData(const uint8_t *aData, size_t aSize, BOARD &aBoard)
Read a Pcbnew s-expression formatted LINE_READER object and returns the appropriate BOARD_ITEM object...
static BOARD * Load(PCB_FILE_T aFileType, const wxString &aFileName, BOARD *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Find the requested #PLUGIN and if found, calls the #PLUGIN::LoadBoard() function on it using the argu...
PCB_FILE_T
The set of file types that the PCB_IO_MGR knows about, and for which there has been a plugin written,...
Definition pcb_io_mgr.h:52
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:54
@ GEDA_PCB
Geda PCB file formats.
Definition pcb_io_mgr.h:65
@ ALTIUM_DESIGNER
Definition pcb_io_mgr.h:59
@ LEGACY
Legacy Pcbnew file formats prior to s-expression.
Definition pcb_io_mgr.h:55
@ CADSTAR_PCB_ARCHIVE
Definition pcb_io_mgr.h:60
static PCB_FILE_T FindPluginTypeFromBoardPath(const wxString &aFileName, int aCtl=0)
Return a plugin type given a path for a board file.
static const wxString ShowType(PCB_FILE_T aFileType)
Return a brief name for a plugin given aFileType enum.
Runs a BOARD_PARSER against a filename or stream and reports results.
bool doParse(BOARD_PARSER &aParser)
bool Parse(const wxString &aFilename)
PCB_IO_MGR::PCB_FILE_T m_pluginType
PCB_PARSE_RUNNER(PCB_IO_MGR::PCB_FILE_T aPluginType, bool aVerbose)
bool Parse(std::istream &aStream)
A small class to help profiling.
Definition profile.h:46
DURATION SinceStart(bool aSinceLast=false)
Definition profile.h:133
STDISTREAM_LINE_READER m_reader
void PrepareStream(std::istream &aStream) override
Take some input stream and prepare it for parsing.
std::unique_ptr< BOARD_ITEM > Parse() override
Actually perform the parsing and return a BOARD_ITEM if successful, or nullptr if not.
LINE_READER that wraps a given std::istream instance.
In order to support fuzz testing, we need to be able to parse from stdin.
virtual void PrepareStream(std::istream &aStream)=0
Take some input stream and prepare it for parsing.
static bool Register(const KI_TEST::UTILITY_PROGRAM &aProgInfo)
Register a utility program factory function against an ID string.
The common library.
static bool registered
static const wxCmdLineEntryDesc g_cmdLineDesc[]
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
@ OK
Tool exited OK.
@ TOOL_SPECIFIC
Tools can define their own statuses from here onwards.
@ BAD_CMDLINE
The command line was not correct for the tool.
Pcbnew s-expression file format parser definition.
std::chrono::microseconds PARSE_DURATION
static const std::map< std::string, PCB_IO_MGR::PCB_FILE_T > pluginTypeMap
Map from command line keys to plugin types.
static PCB_IO_MGR::PCB_FILE_T FindPluginTypeFromParams(const wxString &aExplicitPlugin, const wxString &aPath)
int pcb_parser_main_func(int argc, char **argv)
PARSER_RET_CODES
@ PARSE_FAILED