From 2c0619f69d61c31d75ed34cbcaad426885dc6fd6 Mon Sep 17 00:00:00 2001 From: Wayne Stambaugh Date: Sun, 14 Oct 2012 21:38:32 -0400 Subject: [PATCH] Initial commit of footprint library table code. --- .bzrignore | 2 + CMakeLists.txt | 6 +- common/CMakeLists.txt | 11 +- common/fp_lib_id.cpp | 413 +++++++++++++++++++++++++++++++++++ common/fp_lib_table.cpp | 312 ++++++++++++++++++++++++++ common/fp_lib_table.keywords | 9 + include/fp_lib_id.h | 220 +++++++++++++++++++ include/fp_lib_table.h | 379 ++++++++++++++++++++++++++++++++ pcbnew/pcb_parser.h | 2 +- 9 files changed, 1348 insertions(+), 6 deletions(-) create mode 100644 common/fp_lib_id.cpp create mode 100644 common/fp_lib_table.cpp create mode 100644 common/fp_lib_table.keywords create mode 100644 include/fp_lib_id.h create mode 100644 include/fp_lib_table.h diff --git a/.bzrignore b/.bzrignore index de41d5eea9..4c6cf92eed 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,6 +1,8 @@ common/netlist_keywords.* common/netlist_lexer.h common/pcb_plot_params_lexer.h +common/fp_lib_table_keywords.* +include/fp_lib_table_lexer.h include/netlist_lexer.h eeschema/cmp_library_lexer.h eeschema/cmp_library_keywords.* diff --git a/CMakeLists.txt b/CMakeLists.txt index 40e19d6b4b..7d16569f10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules) # KiCad build options should be added below. # # If you add a new build option, please add it's state to the CopyVersionInfoToClipboard() -# function in common/basicframe.cpp so that build option settings can be includeed in bug +# function in common/basicframe.cpp so that build option settings can be included in bug # reports. # @@ -162,8 +162,6 @@ endif(KICAD_SCRIPTING_MODULES) if(KICAD_SCRIPTING_WXPYTHON) add_definitions(-DKICAD_SCRIPTING_WXPYTHON) - - endif(KICAD_SCRIPTING_WXPYTHON) if(USE_WX_GRAPHICS_CONTEXT) @@ -171,7 +169,7 @@ if(USE_WX_GRAPHICS_CONTEXT) endif(USE_WX_GRAPHICS_CONTEXT) # Allow user to override the default settings for adding images to menu items. By default -# images in menu items are enabled on all plaforms except OSX. This can be over ridden by +# images in menu items are enabled on all platforms except OSX. This can be over ridden by # defining -DUSE_IMAGES_IN_MENUS=ON/OFF to force the preferred behavior. if(NOT DEFINED USE_IMAGES_IN_MENUS) if(NOT APPLE) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index befdb20510..517791a9a4 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -124,6 +124,9 @@ set(PCB_COMMON_SRCS pcb_plot_params_keywords.cpp pcb_keywords.cpp ../pcbnew/pcb_parser.cpp + fp_lib_table_keywords.cpp + fp_lib_id.cpp + fp_lib_table.cpp ) @@ -155,9 +158,15 @@ make_lexer( make_lexer( ${CMAKE_CURRENT_SOURCE_DIR}/pcb.keywords ${PROJECT_SOURCE_DIR}/include/pcb_lexer.h ${CMAKE_CURRENT_SOURCE_DIR}/pcb_keywords.cpp - PCB + PCB_FILE_T ) +# auto-generate pcbnew s-expression footprint library table code. +make_lexer( ${CMAKE_CURRENT_SOURCE_DIR}/fp_lib_table.keywords + ${PROJECT_SOURCE_DIR}/include/fp_lib_table_lexer.h + ${CMAKE_CURRENT_SOURCE_DIR}/fp_lib_table_keywords.cpp + FP_LIB_TABLE_T + ) # The dsntest may not build properly using MS Visual Studio. if(NOT MSVC) diff --git a/common/fp_lib_id.cpp b/common/fp_lib_id.cpp new file mode 100644 index 0000000000..33dd229306 --- /dev/null +++ b/common/fp_lib_id.cpp @@ -0,0 +1,413 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 2010 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include // _() + +#include + + +static inline bool isDigit( char c ) +{ + return c >= '0' && c <= '9'; +} + + +const char* EndsWithRev( const char* start, const char* tail, char separator ) +{ + bool sawDigit = false; + + while( tail > start && isDigit( *--tail ) ) + { + sawDigit = true; + } + + // if sawDigit, tail points to the 'v' here. + + if( sawDigit && tail-3 >= start ) + { + tail -= 3; + + if( tail[0]==separator && tail[1]=='r' && tail[2]=='e' && tail[3]=='v' ) + { + return tail+1; // omit separator, return "revN[N..]" + } + } + + return 0; +} + + +int RevCmp( const char* s1, const char* s2 ) +{ + int r = strncmp( s1, s2, 3 ); + + if( r || strlen(s1)<4 || strlen(s2)<4 ) + { + return r; + } + + int rnum1 = atoi( s1+3 ); + int rnum2 = atoi( s2+3 ); + + return -(rnum1 - rnum2); // swap the sign, higher revs first +} + +//----------------------------------------- + +// These all return -1 on success, or >= 0 if there is an error at a +// particular character offset into their respective arguments. If >=0, +// then that return value gives the character offset of the error. + + +static inline int okLogical( const std::string& aField ) +{ + // std::string::npos is largest positive number, casting to int makes it -1. + // Returning that means success. + return int( aField.find_first_of( ":/" ) ); +} + + +static inline int okBase( const std::string& aField ) +{ + int offset = int( aField.find_first_of( ":/" ) ); + + if( offset != -1 ) + return offset; + + // cannot be empty + if( !aField.size() ) + return 0; + + return offset; // ie. -1 +} + + +static int okRevision( const std::string& aField ) +{ + char rev[32]; // C string for speed + + if( aField.size() >= 4 ) + { + strcpy( rev, "x/" ); + strcat( rev, aField.c_str() ); + + if( EndsWithRev( rev, rev + strlen(rev) ) == rev+2 ) + return -1; // success + } + + return 0; // first character position "is in error", is best we can do. +} + +//----------------------------------------- + + +void FP_LIB_ID::clear() +{ + logical.clear(); + baseName.clear(); + footprintName.clear(); + revision.clear(); +} + + +int FP_LIB_ID::Parse( const std::string& aId ) +{ + clear(); + + const char* rev = EndsWithRev( aId ); + size_t revNdx; + size_t partNdx; + size_t baseNdx; + int offset; + + //============================================== + if( rev ) + { + revNdx = rev - aId.c_str(); + + // no need to check revision, EndsWithRev did that. + revision = aId.substr( revNdx ); + --revNdx; // back up to omit the '/' which preceeds the rev + } + else + revNdx = aId.size(); + + //=============================================== + if( ( partNdx = aId.find( ':' ) ) != aId.npos ) + { + offset = SetLogicalLib( aId.substr( 0, partNdx ) ); + if( offset > -1 ) + { + return offset; + } + ++partNdx; // skip ':' + } + else + partNdx = 0; + + //=============================================== + offset = SetBaseName( aId.substr( baseNdx, revNdx - baseNdx ) ); + + if( offset > -1 ) + { + return offset + baseNdx; + } + + return -1; +} + + +FP_LIB_ID::FP_LIB_ID( const std::string& aId ) throw( PARSE_ERROR ) +{ + int offset = Parse( aId ); + + if( offset != -1 ) + { + THROW_PARSE_ERROR( _( "Illegal character found in FP_LIB_ID string" ), + wxString::FromUTF8( aId.c_str() ), + aId.c_str(), + 0, + offset ); + } +} + + +int FP_LIB_ID::SetLogicalLib( const std::string& aLogical ) +{ + int offset = okLogical( aLogical ); + + if( offset == -1 ) + { + logical = aLogical; + } + + return offset; +} + + +int FP_LIB_ID::SetBaseName( const std::string& aBaseName ) +{ + int offset = okBase( aBaseName ); + + if( offset == -1 ) + { + baseName = aBaseName; + } + + return offset; +} + + +int FP_LIB_ID::SetFootprintName( const std::string& aFootprintName ) +{ + std::string base; + int offset; + int separation = int( aFootprintName.find_first_of( "/" ) ); + + if( separation != -1 ) + { + base = aFootprintName.substr( separation+1 ); + } + else + { + base = aFootprintName; + } + + if( (offset = SetBaseName( base )) != -1 ) + { + return offset + separation + 1; + } + + return -1; +} + + +int FP_LIB_ID::SetRevision( const std::string& aRevision ) +{ + int offset = okRevision( aRevision ); + + if( offset == -1 ) + { + revision = aRevision; + } + + return offset; +} + + +std::string FP_LIB_ID::Format() const +{ + std::string ret; + + if( logical.size() ) + { + ret += logical; + ret += ':'; + } + + ret += baseName; + + if( revision.size() ) + { + ret += '/'; + ret += revision; + } + + return ret; +} + + +std::string FP_LIB_ID::GetFootprintNameAndRev() const +{ + std::string ret; + + ret += baseName; + + if( revision.size() ) + { + ret += '/'; + ret += revision; + } + + return ret; +} + + +std::string FP_LIB_ID::Format( const std::string& aLogicalLib, const std::string& aFootprintName, + const std::string& aRevision ) + throw( PARSE_ERROR ) +{ + std::string ret; + int offset; + + if( aLogicalLib.size() ) + { + offset = okLogical( aLogicalLib ); + + if( offset != -1 ) + { + THROW_PARSE_ERROR( _( "Illegal character found in logical lib name" ), + wxString::FromUTF8( aLogicalLib.c_str() ), + aLogicalLib.c_str(), + 0, + offset ); + } + + ret += aLogicalLib; + ret += ':'; + } + + { + std::string base; + + int separation = int( aFootprintName.find_first_of( "/" ) ); + + if( separation != -1 ) + { + base = aFootprintName.substr( separation+1 ); + } + else + { + base = aFootprintName; + } + + + if( (offset = okBase( base )) != -1 ) + { + THROW_PARSE_ERROR( _( "Illegal character found in base name" ), + wxString::FromUTF8( aRevision.c_str() ), + aRevision.c_str(), + 0, + offset + separation + 1 ); + } + + ret += base; + } + + if( aRevision.size() ) + { + offset = okRevision( aRevision ); + + if( offset != -1 ) + { + THROW_PARSE_ERROR( _( "Illegal character found in revision" ), + wxString::FromUTF8( aRevision.c_str() ), + aRevision.c_str(), + 0, + offset ); + } + + ret += '/'; + ret += aRevision; + } + + return ret; +} + + +#if 0 && defined(DEBUG) + +// build this with Debug CMAKE_BUILD_TYPE + +void FP_LIB_ID::Test() +{ + static const char* lpids[] = { + "/R/rev0", + "passives/R/rev2", + ":passives/R/rev3", + "C/rev22", + "passives/C22", + "R", + "me:R", + // most difficult: + "me:/R/rev0", + "me:R/rev0", + }; + + for( unsigned i=0; i + * Copyright (C) 2012 Wayne Stambaugh + * Copyright (C) 2012 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include + +#include + +#include +#include + + +using namespace FP_LIB_TABLE_T; + + +FP_LIB_TABLE::FP_LIB_TABLE( FP_LIB_TABLE* aFallBackTable ) : + fallBack( aFallBackTable ) +{ + // not copying fall back, simply search aFallBackTable separately + // if "logicalName not found". +} + + +void FP_LIB_TABLE::Parse( FP_LIB_TABLE_LEXER* in ) throw( IO_ERROR, PARSE_ERROR ) +{ + T tok; + + while( ( tok = in->NextTok() ) != T_RIGHT ) + { + // (lib (name "LOGICAL")(type "TYPE")(full_uri "FULL_URI")(options "OPTIONS")) + + if( tok == T_EOF ) + in->Expecting( T_RIGHT ); + + if( tok != T_LEFT ) + in->Expecting( T_LEFT ); + + if( ( tok = in->NextTok() ) != T_fp_lib ) + in->Expecting( T_fp_lib ); + + // (name "LOGICAL_NAME") + in->NeedLEFT(); + + if( ( tok = in->NextTok() ) != T_name ) + in->Expecting( T_name ); + + in->NeedSYMBOLorNUMBER(); + + std::auto_ptr row( new ROW( this ) ); + + row->SetLogicalName( in->CurText() ); + + in->NeedRIGHT(); + + // (type "TYPE") + in->NeedLEFT(); + + if( ( tok = in->NextTok() ) != T_type ) + in->Expecting( T_type ); + + in->NeedSYMBOLorNUMBER(); + + row->SetType( in->CurText() ); + + in->NeedRIGHT(); + + // (uri "FULL_URI") + in->NeedLEFT(); + + if( ( tok = in->NextTok() ) != T_full_uri ) + in->Expecting( T_full_uri ); + + in->NeedSYMBOLorNUMBER(); + + row->SetFullURI( in->CurText() ); + + in->NeedRIGHT(); + + // (options "OPTIONS") + in->NeedLEFT(); + + if( ( tok = in->NextTok() ) != T_options ) + in->Expecting( T_options ); + + in->NeedSYMBOLorNUMBER(); + + row->SetOptions( in->CurText() ); + + in->NeedRIGHT(); + in->NeedRIGHT(); // terminate the (lib..) + + // all logicalNames within this table fragment must be unique, so we do not + // use doReplace in InsertRow(). However a fallBack table can have a + // conflicting logicalName and ours will supercede that one since in + // FindLib() we search this table before any fall back. + if( !InsertRow( row ) ) + { + std::string msg; + + msg += '\''; + msg += row->logicalName; + msg += '\''; + msg += " is a duplicate logical footprint library name"; + THROW_IO_ERROR( msg ); + } + } +} + + +void FP_LIB_TABLE::Format( OUTPUTFORMATTER* out, int nestLevel ) const + throw( IO_ERROR ) +{ + out->Print( nestLevel, "(fp_lib_table\n" ); + + for( ROWS_CITER it = rows.begin(); it != rows.end(); ++it ) + it->second->Format( out, nestLevel+1 ); + + out->Print( nestLevel, ")\n" ); +} + + +void FP_LIB_TABLE::ROW::Format( OUTPUTFORMATTER* out, int nestLevel ) const + throw( IO_ERROR ) +{ + out->Print( nestLevel, "(lib (logical %s)(type %s)(full_uri %s)(options %s))\n", + out->Quotes( logicalName ).c_str(), + out->Quotes( type ).c_str(), + out->Quotes( uri ).c_str(), + out->Quotes( options ).c_str() ); +} + + +std::vector FP_LIB_TABLE::GetLogicalLibs() +{ + // Only return unique logical library names. Use std::set::insert() to + // quietly reject any duplicates, which can happen when encountering a duplicate + // logical lib name from one of the fall back table(s). + + std::set unique; + std::vector ret; + const FP_LIB_TABLE* cur = this; + + do + { + for( ROWS_CITER it = cur->rows.begin(); it!=cur->rows.end(); ++it ) + { + unique.insert( it->second->logicalName ); + } + + } while( ( cur = cur->fallBack ) != 0 ); + + // return a sorted, unique set of logical lib name std::vector to caller + for( std::set::const_iterator it = unique.begin(); it!=unique.end(); ++it ) + ret.push_back( *it ); + + return ret; +} + + +MODULE* FP_LIB_TABLE::LookupFootprint( const FP_LIB_ID& aFootprintId ) + throw( IO_ERROR ) +{ + PLUGIN* plugin = lookupLib( aFootprintId ); + + return plugin->FootprintLoad( wxString( aFootprintId.GetBaseName().c_str() ), + wxString( aFootprintId.GetLogicalLib().c_str() ) ); +} + + +PLUGIN* FP_LIB_TABLE::lookupLib( const FP_LIB_ID& aFootprintId ) + throw( IO_ERROR ) +{ + if( aFootprintId.GetLogicalLib().size() ) + { + ROW* row = FindRow( aFootprintId.GetLogicalLib() ); + + if( !row ) + { + std::string msg = "lib table contains no logical lib '"; + msg += aFootprintId.GetLogicalLib(); + msg += '\''; + THROW_IO_ERROR( msg ); + } + + if( !row->lib ) + { + loadLib( row ); + } + + assert( row->lib ); // fix loadLib() to throw if cannot load + + return row->lib; + } + + std::string msg = "lookupLib() requires logicalLibName"; + THROW_IO_ERROR( msg ); +} + + +void FP_LIB_TABLE::loadLib( ROW* aRow ) throw( IO_ERROR ) +{ + assert( !aRow->lib ); // caller should know better. + + const std::string& type = aRow->GetType(); + + if( !type.compare( "dir" ) ) + { + // @todo Look up plug in here. + } + +/* + else if( !type.compare( "schematic" ) ) + { + // @todo code and load SCHEMATIC_LIB_SOURCE + } + + else if( !type.compare( "subversion" ) ) + { + // @todo code and load SVN_LIB_SOURCE + } + + else if( !type.compare( "http" ) ) + { + // @todo code and load HTTP_LIB_SOURCE + } +*/ + else + { + std::string msg = "cannot load unknown footprint library type: '"; + msg += type; + msg += '\''; + THROW_IO_ERROR( msg ); + } +} + + +FP_LIB_TABLE::ROW* FP_LIB_TABLE::FindRow( const std::string& aLogicalName ) const +{ + // this function must be *super* fast, so therefore should not instantiate + // anything which would require using the heap. This function is the reason + // ptr_map<> was used instead of ptr_set<>, which would have required + // instantiating a ROW just to find a ROW. + const FP_LIB_TABLE* cur = this; + + do + { + ROWS_CITER it = cur->rows.find( aLogicalName ); + + if( it != cur->rows.end() ) + { + // reference: http://myitcorner.com/blog/?p=361 + return (FP_LIB_TABLE::ROW*) it->second; // found + } + + // not found, search fall back table(s), if any + } while( ( cur = cur->fallBack ) != 0 ); + + return 0; // not found +} + + +bool FP_LIB_TABLE::InsertRow( std::auto_ptr& aRow, bool doReplace ) +{ + // this does not need to be super fast. + + ROWS_CITER it = rows.find( aRow->logicalName ); + + if( it == rows.end() ) + { + // be careful here, key is needed because aRow can be + // release()ed before logicalName is captured. + const std::string& key = aRow->logicalName; + rows.insert( key, aRow ); + return true; + } + + if( doReplace ) + { + rows.erase( aRow->logicalName ); + + // be careful here, key is needed because aRow can be + // release()ed before logicalName is captured. + const std::string& key = aRow->logicalName; + rows.insert( key, aRow ); + return true; + } + + return false; +} + diff --git a/common/fp_lib_table.keywords b/common/fp_lib_table.keywords new file mode 100644 index 0000000000..6a678ed707 --- /dev/null +++ b/common/fp_lib_table.keywords @@ -0,0 +1,9 @@ +fp_lib_table +name +type +kicad +legacy +eagle +full_uri +options +fp_lib diff --git a/include/fp_lib_id.h b/include/fp_lib_id.h new file mode 100644 index 0000000000..f12bde097a --- /dev/null +++ b/include/fp_lib_id.h @@ -0,0 +1,220 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 2010 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef _FP_LIB_ID_H_ +#define _FP_LIB_ID_H_ + +#include + + +/** + * Class FP_LIB_ID + * (aka GUID) is a Logical Part ID and consists of various portions much like a URI. + * It is a container for the separated portions of a logical part id std::string so they + * can be accessed individually. The various portions of an FP_LIB_ID are: + * logicalLibraryName, category, baseName, and revision. Only the baseName is + * mandatory. There is another construct called "footprintName" which consists of + * [category/]baseName. That is the category followed by a slash, but only if + * the category is not empty. + *

+ * footprintName = [category/]baseName + *

+ * Example FP_LIB_ID string: + * "smt:R_0805". + *

+ *

    + *
  • "smt" is the logical library name. + *
  • "R" is the footprint name. + *
  • "rev6" is the revision, which is optional. If missing then its + * / delimiter should also not be present. A revision must begin with + * "rev" and be followed by at least one or more decimal digits. + *
+ * @author Dick Hollenbeck + */ +class FP_LIB_ID // aka GUID +{ +public: + + FP_LIB_ID() {} + + /** + * Constructor FP_LIB_ID + * takes \a aId string and parses it. A typical FP_LIB_ID string uses a logical + * library name followed by a footprint name. + * e.g.: "smt:R_0805", or + * e.g.: "mylib:R_0805" + */ + FP_LIB_ID( const std::string& aId ) throw( PARSE_ERROR ); + + /** + * Function Parse + * [re-]stuffs this FP_LIB_ID with the information from @a aId. + * @return int - minus 1 (i.e. -1) means success, >= 0 indicates the + * character offset into aId at which an error was detected. + */ + int Parse( const std::string& aId ); + + /** + * Function GetLogicalLib + * returns the logical library portion of a FP_LIB_ID. There is not Set accessor + * for this portion since it comes from the library table and is considered + * read only here. + */ + const std::string& GetLogicalLib() const + { + return logical; + } + + /** + * Function SetCategory + * overrides the logical lib name portion of the FP_LIB_ID to @a aLogical, and can be empty. + * @return int - minus 1 (i.e. -1) means success, >= 0 indicates the + * character offset into the parameter at which an error was detected, usually + * because it contained '/' or ':'. + */ + int SetLogicalLib( const std::string& aLogical ); + + /** + * Function GetBaseName + * returns the part name without the category. + */ + const std::string& GetBaseName() const + { + return baseName; + } + + /** + * Function SetBaseName + * overrides the base name portion of the FP_LIB_ID to @a aBaseName + * @return int - minus 1 (i.e. -1) means success, >= 0 indicates the + * character offset into the parameter at which an error was detected, usually + * because it contained '/' or ':', or is blank. + */ + int SetBaseName( const std::string& aBaseName ); + + /** + * Function GetFootprintName + * returns the part name, i.e. category/baseName without revision. + */ + const std::string& GetFootprintName() const + { + return footprintName; + } + + /** + * Function GetFootprintNameAndRev + * returns the part name with revision if any, i.e. baseName[/revN..] + */ + std::string GetFootprintNameAndRev() const; + + /** + * Function SetFootprintName + * overrides the part name portion of the FP_LIB_ID to @a aFootprintName + * @return int - minus 1 (i.e. -1) means success, >= 0 indicates the + * character offset into the parameter at which an error was detected, usually + * because it contained more than one '/', or one or more ':', or is blank. + * A single '/' is allowed, since that is used to separate the category from the + * base name. + */ + int SetFootprintName( const std::string& aFootprintName ); + + /** + * Function GetRevision + * returns the revision portion of the FP_LIB_ID. + */ + const std::string& GetRevision() const + { + return revision; + } + + /** + * Function SetRevision + * overrides the revision portion of the FP_LIB_ID to @a aRevision and must + * be in the form "rev" where "" is "1", "2", etc. + * @return int - minus 1 (i.e. -1) means success, >= 0 indicates the + * character offset into the parameter at which an error was detected, + * because it did not look like "rev23" + */ + int SetRevision( const std::string& aRevision ); + + /** + * Function Format + * returns the full text of the FP_LIB_ID. + */ + std::string Format() const; + + /** + * Function Format + * returns a std::string in the proper format as an FP_LIB_ID for a combination of + * aLogicalLib, aFootprintName, and aRevision. + * @throw PARSE_ERROR if any of the pieces are illegal. + */ + static std::string Format( const std::string& aLogicalLib, const std::string& aFootprintName, + const std::string& aRevision="" ) + throw( PARSE_ERROR ); + + void clear(); + +#if defined(DEBUG) + static void Test(); +#endif + +protected: + std::string logical; ///< logical lib name or empty + std::string baseName; ///< without category + std::string revision; ///< "revN[N..]" or empty + std::string footprintName; ///< cannot be set directory, set via SetBaseName() & SetCategory() +}; + +/** + * Function EndsWithRev + * returns a pointer to the final string segment: "revN[N..]" or NULL if none. + * @param start is the beginning of string segment to test, the partname or + * any middle portion of it. + * @param tail is a pointer to the terminating nul, or one past inclusive end of + * segment, i.e. the string segment of interest is [start,tail) + * @param separator is the separating byte, expected: '.' or '/', depending on context. + */ +const char* EndsWithRev( const char* start, const char* tail, char separator = '/' ); + +static inline const char* EndsWithRev( const std::string& aFootprintName, char separator = '/' ) +{ + return EndsWithRev( aFootprintName.c_str(), aFootprintName.c_str()+aFootprintName.size(), + separator ); +} + + +/** + * Function RevCmp + * compares two rev strings in a way like strcmp() except that the highest numbered + * revision is considered first in the sort order. The function probably won't work + * unless you give it two rev strings. + * @param s1 is a rev string like "rev10" + * @param s2 is a rev string like "rev1". + * @return int - either negative, zero, or positive depending on whether the revision + * is greater, equal, or less on the left hand side. + */ +int RevCmp( const char* s1, const char* s2 ); + +#endif // _FP_LIB_ID_H_ diff --git a/include/fp_lib_table.h b/include/fp_lib_table.h new file mode 100644 index 0000000000..c304864da1 --- /dev/null +++ b/include/fp_lib_table.h @@ -0,0 +1,379 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 2012 Wayne Stambaugh + * Copyright (C) 2012 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef _FP_LIB_TABLE_H_ +#define _FP_LIB_TABLE_H_ + + +#include + +#include + + +class OUTPUTFORMATTER; + + +class MODULE; + +/** + * Class FP_LIB_TABLE + * holds FP_LIB_TABLE::ROW records, and can be searched based on logical library name. + *

+ * This class owns the footprint library table, which is like fstab in concept and maps + * logical library name to the library URI, type, and options. It is heavily based on the SWEET + * parser work done by Dick Hollenbeck and can be seen in new/sch_lib_table.h. A footprint + * library table had the following columns: + *

    + *
  • Logical Library Name (Nickname) + *
  • Library Type, used to determine which plugin to load to access the library. + *
  • Library URI. The full URI to the library source, form dependent on Type. + *
  • Options, used for as yet to be defined information such as user names or passwords + *
+ *

+ * The Library Type can be one of: + *

    + *
  • "file" + *
  • "ftp" + *
  • "http" + *
+ *

+ * For now, the Library URI types needed to support the various types can be one of those + * shown below, which are typical of each type: + *

    + *
  • "file://C:/mylibdir" + *
  • "ftp://kicad.org/partlib/trunk" + *
  • "http://kicad.org/partlib" + *
+ *

+ * The footprint library table is built up from several additive entries (table fragments), + * and the final table is a (conceptual) merging of the table fragments. Two + * anticipated sources of the entries are a personal table saved in the KiCad configuration + * and a project resident table that resides in project file. The project footprint table + * entries are considered a higher priority in the final dynamically assembled library table. + * An row in the project file contribution to the library table takes precedence over the + * personal table if there is a collision of logical library names. Otherwise, the entries + * simply combine without issue to make up the applicable library table. + * + * @author Wayne Stambaugh + */ +class FP_LIB_TABLE +{ +public: + + /** + * Class ROW + * holds a record identifying a footprint library accessed by the appropriate #PLUGIN + * object in the #FP_LIB_TABLE. + */ + class ROW + { + friend class FP_LIB_TABLE; + + public: + + /** + * Function GetLogicalName + * returns the logical name of this library table row. + */ + const std::string& GetLogicalName() const + { + return logicalName; + } + + /** + * Function GetType + * returns the type of LIB represented by this record. + */ + const std::string& GetType() const + { + return type; + } + + /** + * Function GetFullURI + * returns the full location specifying URI for the LIB. + */ + const std::string& GetFullURI() const + { + return uri; + } + + /** + * Function GetOptions + * returns the options string, which may hold a password or anything else needed to + * instantiate the underlying LIB_SOURCE. + */ + const std::string& GetOptions() const + { + return options; + } + + ~ROW() + { + delete lib; + } + + /** + * Function Format + * serializes this object as utf8 text to an OUTPUTFORMATTER, and tries to + * make it look good using multiple lines and indentation. + * @param out is an #OUTPUTFORMATTER + * @param nestLevel is the indentation level to base all lines of the output. + * Actual indentation will be 2 spaces for each nestLevel. + */ + void Format( OUTPUTFORMATTER* out, int nestLevel ) const + throw( IO_ERROR ); + + protected: + + ROW( FP_LIB_TABLE* aOwner ) : + owner( aOwner ), + lib( 0 ) + {} + + /** + * Function SetLogicalName + * changes the logical name of this library, useful for an editor. + */ + void SetLogicalName( const std::string& aLogicalName ) + { + logicalName = aLogicalName; + } + + /** + * Function SetType + * changes the type represented by this record. + */ + void SetType( const std::string& aType ) + { + type = aType; + } + + /** + * Function SetFullURI + * changes the full URI for the library, useful from a library table editor. + */ + void SetFullURI( const std::string& aFullURI ) + { + uri = aFullURI; + } + + /** + * Function SetOptions + * changes the options string for this record, and is useful from + * the library table editor. + */ + void SetOptions( const std::string& aOptions ) + { + options = aOptions; + } + + private: + FP_LIB_TABLE* owner; + std::string logicalName; + std::string type; + std::string uri; + std::string options; + + PLUGIN* lib; ///< ownership of the loaded LIB is here + }; + + /** + * Constructor FP_LIB_TABLE + * builds a library table by pre-pending this table fragment in front of + * @a aFallBackTable. Loading of this table fragment is done by using Parse(). + * + * @param aFallBackTable is another FP_LIB_TABLE which is searched only when + * a record is not found in this table. No ownership is + * taken of aFallBackTable. + */ + FP_LIB_TABLE( FP_LIB_TABLE* aFallBackTable = NULL ); + + /** + * Function Parse + * fills this table fragment from information in the input stream \a aParser, which + * is a DSNLEXER customized for the grammar needed to describe instances of this object. + * The entire textual element spec is
+ * + *

+     * (fp_lib_table
+     *   (lib (name LOGICAL)(type TYPE)(uri FULL_URI)(options OPTIONS))
+     *   (lib (name LOGICAL)(type TYPE)(uri FULL_URI)(options OPTIONS))
+     *   (lib (name LOGICAL)(type TYPE)(uri FULL_URI)(options OPTIONS))
+     *  )
+     * 
+ * + * When this function is called, the input token stream given by \a aParser + * is assumed to be positioned at the '^' in the following example, i.e. just + * after the identifying keyword and before the content specifying stuff. + *
+ * (lib_table ^ (....) ) + * + * @param aParser is the input token stream of keywords and symbols. + */ + void Parse( FP_LIB_TABLE_LEXER* aParser ) throw( IO_ERROR, PARSE_ERROR ); + + /** + * Function Format + * serializes this object as utf8 text to an #OUTPUTFORMATTER, and tries to + * make it look good using multiple lines and indentation. + * + * @param out is an #OUTPUTFORMATTER + * @param nestLevel is the indentation level to base all lines of the output. + * Actual indentation will be 2 spaces for each nestLevel. + */ + void Format( OUTPUTFORMATTER* out, int nestLevel ) const throw( IO_ERROR ); + + /** + * Function LookupPart + * finds and loads a MODULE, and parses it. As long as the part is + * accessible in any LIB_SOURCE, opened or not opened, this function + * will find it and load it into its containing LIB, even if that means + * having to open a LIB in this table that was not previously opened. + * + * @param aFootprintId The fully qualified name of the footprint to look up. + * + * @return MODULE* - this will never be NULL, and no ownership is transferred because + * all MODULEs live in LIBs. You only get to point to them in some LIB. If the MODULE + * cannot be found, then an exception is thrown. + * + * @throw IO_ERROR if any problem occurs or if the footprint cannot be found. + */ + MODULE* LookupFootprint( const FP_LIB_ID& aFootprintId ) throw( IO_ERROR ); + + /** + * Function GetLogicalLibs + * returns the logical library names, all of them that are pertinent to + * a lookup done on this FP_LIB_TABLE. + */ + std::vector GetLogicalLibs(); + + //-------------------------------------------------------- + // the returning of a const std::string* tells if not found, but might be too + // promiscuous? + + /** + * Function GetURI + * returns the full library path from a logical library name. + * @param aLogicalLibraryName is the short name for the library of interest. + * @return const std::string* - or NULL if not found. + */ + const std::string* GetURI( const std::string& aLogicalLibraryName ) const + { + const ROW* row = FindRow( aLogicalLibraryName ); + return row ? &row->uri : 0; + } + + /** + * Function GetType + * returns the type of a logical library. + * @param aLogicalLibraryName is the short name for the library of interest. + * @return const std::string* - or NULL if not found. + */ + const std::string* GetType( const std::string& aLogicalLibraryName ) const + { + const ROW* row = FindRow( aLogicalLibraryName ); + return row ? &row->type : 0; + } + + /** + * Function GetLibOptions + * returns the options string for \a aLogicalLibraryName. + * @param aLogicalLibraryName is the short name for the library of interest. + * @return const std::string* - or NULL if not found. + */ + const std::string* GetLibOptions( const std::string& aLogicalLibraryName ) const + { + const ROW* row = FindRow( aLogicalLibraryName ); + return row ? &row->options : 0; + } + + //------------------------------------------------------- + +#if 1 || defined(DEBUG) + /// implement the tests in here so we can honor the privilege levels of the + /// accessors, something difficult to do from int main(int, char**) + void Test(); +#endif + +protected: // only a table editor can use these + + /** + * Function InsertRow + * adds aRow if it does not already exist or if doReplace is true. If doReplace + * is not true and the key for aRow already exists, the function fails and returns false. + * The key for the table is the logicalName, and all in this table must be unique. + * @param aRow is the new row to insert, or to forcibly add if doReplace is true. + * @param doReplace if true, means insert regardless of whether aRow's key already + * exists. If false, then fail if the key already exists. + * @return bool - true if the operation succeeded. + */ + bool InsertRow( std::auto_ptr& aRow, bool doReplace = false ); + + /** + * Function FindRow + * returns a #ROW* if aLogicalName is found in this table or in any chained + * fallBack table fragment, else NULL. + */ + ROW* FindRow( const std::string& aLogicalName ) const; + +private: + + /** + * Function lookupLib + * finds or loads a LIB based on @a aLogicalPartID or @a aFallBackLib. + * If the LIB is already loaded then it is returned as is, else it is loaded. + * + * @param aLogicalPartID holds the partName and may also hold the logicalLibName. If + * logicalLibName is empty, then @a aFallBackLib should not be NULL. + * + * @param aFallBackLib is used only if aLogicalPartID has an empty logicalLibName. + * This is for the case when an LPID has no logicalLibName because the LPID is using + * a partName from the same LIB as was the referring content. + * + * @return PLUGIN* - this will never be NULL, and no ownership is transfered because + * all LIBs live in the FP_LIB_TABLEs. You only get to point to them in some FP_LIB_TABLE. + * If the LIB cannot be found, then an exception is thrown. + * + * @throw IO_ERROR if any problem occurs or if the LIB cannot be found or cannot be loaded. + */ + PLUGIN* lookupLib( const FP_LIB_ID& aLogicalPartID ) throw( IO_ERROR ); + + /** + * Function loadLib + * loads a LIB using information in @a aRow. Call only if LIB not + * already loaded. + */ + void loadLib( ROW* aRow ) throw( IO_ERROR ); + + typedef boost::ptr_map ROWS; + typedef ROWS::iterator ROWS_ITER; + typedef ROWS::const_iterator ROWS_CITER; + + ROWS rows; + FP_LIB_TABLE* fallBack; +}; + +#endif // _FP_LIB_TABLE_H_ diff --git a/pcbnew/pcb_parser.h b/pcbnew/pcb_parser.h index e9ad61f4eb..91f8c13e86 100644 --- a/pcbnew/pcb_parser.h +++ b/pcbnew/pcb_parser.h @@ -34,7 +34,7 @@ #include -using namespace PCB; +using namespace PCB_FILE_T; class BOARD;