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