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 return PCB_IO_MGR::Load( m_fileType, m_fileName, {}, nullptr, nullptr );
78 }
79
80private:
82 wxString m_fileName;
83};
84
85
92{
93public:
101 virtual void PrepareStream( std::istream& aStream ) = 0;
102};
103
104
106{
107public:
108 void PrepareStream( std::istream& aStream ) override { m_reader.SetStream( aStream ); }
109
110 std::unique_ptr<BOARD_ITEM> Parse() override
111 {
112 PCB_IO_KICAD_SEXPR_PARSER parser( &m_reader, nullptr, nullptr );
113 return std::unique_ptr<BOARD_ITEM>{ parser.Parse() };
114 }
115
116private:
118};
119
120
122{
123public:
124 void PrepareStream( std::istream& aStream ) override
125 {
126 // Allegro parser expects to mmap a file, so we need to
127 // dump it all in memory to simulate that.
128 m_buffer.assign( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>() );
129
130 // Check if stream reading failed
131 if( aStream.fail() && !aStream.eof() )
132 {
133 THROW_IO_ERROR( _( "Failed to read from input stream" ) );
134 }
135 }
136
137 std::unique_ptr<BOARD_ITEM> Parse() override
138 {
139 PCB_IO_ALLEGRO allegroParser;
140 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
141
142 if( !allegroParser.LoadBoardFromData( m_buffer.data(), m_buffer.size(), *board ) )
143 {
144 return nullptr;
145 }
146
147 return board;
148 }
149
150private:
151 std::vector<uint8_t> m_buffer;
152};
153
154
159{
160public:
161 PCB_PARSE_RUNNER( PCB_IO_MGR::PCB_FILE_T aPluginType, bool aVerbose ) :
162 m_pluginType( aPluginType ),
163 m_verbose( aVerbose )
164 {
165 }
166
167 bool Parse( std::istream& aStream )
168 {
169 std::unique_ptr<STREAM_PARSER> parser;
170
171 switch( m_pluginType )
172 {
173 case PCB_IO_MGR::KICAD_SEXP: parser = std::make_unique<SEXPR_STREAM_PARSER>(); break;
174 case PCB_IO_MGR::ALLEGRO: parser = std::make_unique<ALLEGRO_BRD_STREAM_PARSER>(); break;
175 default:
176 std::cerr << fmt::format( "Unsupported plugin type for streaming input: {}",
177 static_cast<int>( m_pluginType ) )
178 << std::endl;
179 return false;
180 }
181
182 wxCHECK( parser, false );
183
184 try
185 {
186 parser->PrepareStream( aStream );
187 }
188 catch( const IO_ERROR& e )
189 {
190 std::cerr << fmt::format( "Error preparing stream: {}", e.What().ToStdString() ) << std::endl;
191 return false;
192 }
193
194 return doParse( *parser );
195 }
196
197 bool Parse( const wxString& aFilename )
198 {
199 FILE_PARSER parser( m_pluginType, aFilename );
200 return doParse( parser );
201 }
202
203private:
204 bool doParse( BOARD_PARSER& aParser )
205 {
206 std::unique_ptr<BOARD_ITEM> board;
207 PARSE_DURATION duration{};
208
209 try
210 {
211 PROF_TIMER timer;
212 board = aParser.Parse();
213 duration = timer.SinceStart<PARSE_DURATION>();
214 }
215 catch( const IO_ERROR& e )
216 {
217 std::cerr << "Parsing failed: " << e.What() << std::endl;
218 }
219
220 if( m_verbose )
221 {
222 std::cout << fmt::format( "Took: {}us", duration.count() ) << std::endl;
223
224 if( board )
225 std::cout << fmt::format( " {} nets", board->GetBoard()->GetNetCount() ) << std::endl;
226 }
227
228 return board != nullptr;
229 }
230
233};
234
235
236static const wxCmdLineEntryDesc g_cmdLineDesc[] = {
237 {
238 wxCMD_LINE_SWITCH,
239 "h",
240 "help",
241 _( "displays help on the command line parameters" ).mb_str(),
242 wxCMD_LINE_VAL_NONE,
243 wxCMD_LINE_OPTION_HELP,
244 },
245 {
246 wxCMD_LINE_SWITCH,
247 "v",
248 "verbose",
249 _( "print parsing information" ).mb_str(),
250 },
251 {
252 wxCMD_LINE_OPTION,
253 "p",
254 "plugin",
255 _( "parser plugin to use (kicad, allegro, etc.)" ).mb_str(),
256 wxCMD_LINE_VAL_STRING,
257 },
258 {
259 wxCMD_LINE_SWITCH,
260 nullptr,
261 "list-plugins",
262 _( "list available plugins and exit" ).mb_str(),
263 },
264 {
265 wxCMD_LINE_OPTION,
266 "l",
267 "loop",
268 _( "number of times to loop when parsing from stdin (for AFL)" ).mb_str(),
269 wxCMD_LINE_VAL_NUMBER,
270 },
271 {
272 wxCMD_LINE_PARAM,
273 nullptr,
274 nullptr,
275 _( "input file" ).mb_str(),
276 wxCMD_LINE_VAL_STRING,
277 wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE,
278 },
279 {
280 wxCMD_LINE_NONE,
281 }
282};
283
284
289
290
294static const std::map<std::string, PCB_IO_MGR::PCB_FILE_T> pluginTypeMap = {
295 { "kicad", PCB_IO_MGR::KICAD_SEXP },
296 { "legacy", PCB_IO_MGR::LEGACY },
297 { "allegro", PCB_IO_MGR::ALLEGRO },
298 { "altium", PCB_IO_MGR::ALTIUM_DESIGNER },
299 { "cadstar", PCB_IO_MGR::CADSTAR_PCB_ARCHIVE },
300 { "eagle", PCB_IO_MGR::EAGLE },
301 { "easyeda", PCB_IO_MGR::EASYEDA },
302 { "easyedapro", PCB_IO_MGR::EASYEDAPRO },
303 { "fabmaster", PCB_IO_MGR::FABMASTER },
304 { "geda", PCB_IO_MGR::GEDA_PCB },
305 { "pads", PCB_IO_MGR::PADS },
306 { "pcad", PCB_IO_MGR::PCAD },
307 { "solidworks", PCB_IO_MGR::SOLIDWORKS_PCB },
308 // { "ipc2581", PCB_IO_MGR::IPC2581 }, // readers
309 // { "odbpp", PCB_IO_MGR::ODBPP },
310};
311
312
313static PCB_IO_MGR::PCB_FILE_T FindPluginTypeFromParams( const wxString& aExplicitPlugin, const wxString& aPath )
314{
315 if( aExplicitPlugin == "auto" )
316 {
317 // Try to guess the plugin type from the first file
319 }
320
321 auto pluginIt = pluginTypeMap.find( aExplicitPlugin.ToStdString() );
322 if( pluginIt == pluginTypeMap.end() )
323 {
325 }
326 return pluginIt->second;
327}
328
329
330int pcb_parser_main_func( int argc, char** argv )
331{
332#ifdef __AFL_COMPILER
333 __AFL_INIT();
334#endif
335
336 wxMessageOutput::Set( new wxMessageOutputStderr );
337 wxCmdLineParser cl_parser( argc, argv );
338 cl_parser.SetDesc( g_cmdLineDesc );
339 cl_parser.AddUsageText( _( "This program parses PCB files, either from the stdin stream or "
340 "from the given filenames. This can be used either for standalone "
341 "testing of the parser or for fuzz testing." ) );
342
343 int cmd_parsed_ok = cl_parser.Parse();
344 if( cmd_parsed_ok != 0 )
345 {
346 // Help and invalid input both stop here
347 return ( cmd_parsed_ok == -1 ) ? KI_TEST::RET_CODES::OK : KI_TEST::RET_CODES::BAD_CMDLINE;
348 }
349
350 const bool verbose = cl_parser.Found( "verbose" );
351
352 if( cl_parser.Found( "list-plugins" ) )
353 {
354 for( const auto& [name, type] : pluginTypeMap )
355 {
356 std::cout << name << std::endl;
357 }
358 std::cout << "auto" << std::endl;
359
361 }
362
363 bool ok = true;
364 const size_t file_count = cl_parser.GetParamCount();
365
366 wxString plugin( "auto" );
367 cl_parser.Found( "plugin", &plugin );
368
369 long aflLoopCount = 1;
370 cl_parser.Found( "loop", &aflLoopCount );
371
372 if( file_count == 0 && plugin == "auto" )
373 {
374 std::cerr << "When parsing from stdin, you must specify the plugin type with -p" << std::endl;
376 }
377
378 const PCB_IO_MGR::PCB_FILE_T pluginType =
379 FindPluginTypeFromParams( plugin, file_count > 0 ? cl_parser.GetParam( 0 ) : wxString( "" ) );
380
381 if( pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
382 {
383 std::cerr << fmt::format( "Failed to determine plugin type for input using plugin {}", plugin.ToStdString() )
384 << std::endl;
386 }
387
388 if( verbose )
389 {
390 std::cout << "Using plugin type: " << PCB_IO_MGR::ShowType( pluginType ) << std::endl;
391 }
392
393 PCB_PARSE_RUNNER runner( pluginType, verbose );
394
395 std::vector<std::string> failedFiles;
396
397 if( file_count == 0 )
398 {
399 // Parse the file provided on stdin - used by AFL to drive the
400 // program
401#ifdef __AFL_COMPILER
402 while( __AFL_LOOP( aflLoopCount ) )
403#endif
404 {
405 ok = runner.Parse( std::cin );
406 }
407 }
408 else
409 {
410 // Parse 'n' files given on the command line
411 // (this is useful for input minimisation (e.g. afl-tmin) as
412 // well as manual testing
413 for( size_t i = 0; i < file_count; i++ )
414 {
415 const wxString filename = cl_parser.GetParam( i );
416
417 if( verbose )
418 std::cout << fmt::format( "Parsing: {}", filename.ToStdString() ) << std::endl;
419
420 if( !runner.Parse( filename ) )
421 {
422 ok = false;
423 failedFiles.push_back( filename.ToStdString() );
424 }
425 }
426 }
427
428 for( const auto& failedFile : failedFiles )
429 {
430 std::cerr << fmt::format( "Failed to parse: {}", failedFile ) << std::endl;
431 }
432
433 if( !ok )
435
437}
438
439
440static bool registered = UTILITY_REGISTRY::Register( { "pcb_parser",
441 "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
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...
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:66
@ 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 std::unique_ptr< BOARD > Load(PCB_FILE_T aFileType, const wxString &aFileName, 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...
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.
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
std::chrono::microseconds PARSE_DURATION
@ 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.
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