diff --git a/new/sch_dir_lib_source.cpp b/new/sch_dir_lib_source.cpp index 0e7f0a8f32..042daa2a50 100644 --- a/new/sch_dir_lib_source.cpp +++ b/new/sch_dir_lib_source.cpp @@ -400,11 +400,11 @@ DIR_LIB_SOURCE::~DIR_LIB_SOURCE() void DIR_LIB_SOURCE::GetCategoricalPartNames( STRINGS* aResults, const STRING& aCategory ) throw( IO_ERROR ) { - PN_ITER limit = aCategory.size() ? + PN_ITER end = aCategory.size() ? partnames.lower_bound( aCategory + char( '/' + 1 ) ) : partnames.end(); - PN_ITER it = aCategory.size() ? + PN_ITER it = aCategory.size() ? partnames.upper_bound( aCategory + "/" ) : partnames.begin(); @@ -414,7 +414,7 @@ void DIR_LIB_SOURCE::GetCategoricalPartNames( STRINGS* aResults, const STRING& a { STRING partName; - while( it != limit ) + while( it != end ) { const char* rev = endsWithRev( *it ); @@ -434,7 +434,32 @@ void DIR_LIB_SOURCE::GetCategoricalPartNames( STRINGS* aResults, const STRING& a else { - while( it != limit ) + while( it != end ) + aResults->push_back( *it++ ); + } +} + + +void DIR_LIB_SOURCE::GetRevisions( STRINGS* aResults, const STRING& aPartName ) + throw( IO_ERROR ) +{ + aResults->clear(); + + if( useVersioning ) + { + STRING partName; + + const char* rev = endsWithRev( aPartName ); + if( rev ) + // partName is substring which omits the rev and the separator + partName.assign( aPartName, 0, rev - aPartName.c_str() - 1 ); + else + partName = aPartName; + + PN_ITER it = partnames.upper_bound( partName +'/' ); + PN_ITER end = partnames.lower_bound( partName + char( '/' +1 ) ); + + while( it != end ) aResults->push_back( *it++ ); } } diff --git a/new/sch_dir_lib_source.h b/new/sch_dir_lib_source.h index d7a7b9667c..b6336cfd9c 100644 --- a/new/sch_dir_lib_source.h +++ b/new/sch_dir_lib_source.h @@ -174,10 +174,8 @@ protected: void GetCategoricalPartNames( STRINGS* aResults, const STRING& aCategory = "" ) throw( IO_ERROR ); - void GetRevisions( STRINGS* aResults, const STRING& aPartName ) throw( IO_ERROR ) - { - // @todo - } + void GetRevisions( STRINGS* aResults, const STRING& aPartName ) + throw( IO_ERROR ); void FindParts( STRINGS* aResults, const STRING& aQuery ) throw( IO_ERROR ) { diff --git a/new/sch_lib.cpp b/new/sch_lib.cpp index 1770de0477..a9c567d998 100644 --- a/new/sch_lib.cpp +++ b/new/sch_lib.cpp @@ -25,19 +25,95 @@ #include // std::auto_ptr #include + #include #include #include #include #include + +#if 1 + +#include + +/* + +The LIB part cache consist of a std::map of partnames without revisions at the top level. +Each top level map entry can point to another std::map which it owns and holds all the revisions +for that part name. At any point in the tree, there can be NULL pointers which +allow for lazy loading, including the very top most root pointer itself, which +is PARTS* parts. We use the key to hold the partName at one level, and revision +at the deeper nested level, and that key information may not be present within +right hand side of the map tuple. + +1) Only things which are asked for are done. +2) Anything we learn we remember. + +*/ + +namespace SCH { + +class PART_REVS : public std::map< STRING, PART* > +{ + // @todo provide an integer sort on revN.. strings here. + +public: + ~PART_REVS() + { + for( iterator it = begin(); it != end(); ++it ) + { + delete it->second; // second may be NULL, no problem + } + } +}; + +class PARTS : public std::map< STRING, PART_REVS* > +{ +public: + ~PARTS() + { + for( iterator it = begin(); it != end(); ++it ) + { + delete it->second; // second may be NULL, no problem + } + } +}; + +} // namespace SCH + + +#else // was nothing but grief: + +#include + +namespace SCH { + +/// PARTS' key is revision, like "rev12", PART pointer may be null until loaded. +typedef boost::ptr_map< STRING, boost::nullable > PARTS; +typedef PARTS::iterator PARTS_ITER; +typedef PARTS::const_iterator PARTS_CITER; + + +/// PART_REVS' key is part name, w/o rev, PART pointer may be null until loaded. +typedef boost::ptr_map< STRING, boost::nullable > PART_REVS; +typedef PART_REVS::iterator PART_REVS_ITER; +typedef PART_REVS::const_iterator PART_REVS_CITER; + +} // namespace SCH + +#endif + + using namespace SCH; LIB::LIB( const STRING& aLogicalLibrary, LIB_SOURCE* aSource, LIB_SINK* aSink ) : - name( aLogicalLibrary ), + logicalName( aLogicalLibrary ), source( aSource ), - sink( aSink ) + sink( aSink ), + cachedCategories( false ), + parts( 0 ) { } @@ -46,25 +122,99 @@ LIB::~LIB() { delete source; delete sink; + delete parts; +} + + +const PART* LIB::findPart( const LPID& aLPID ) throw( IO_ERROR ) +{ + if( !parts ) + { + parts = new PARTS; + + source->GetCategoricalPartNames( &vfetch ); + + // insert a PART_REVS for each part name + for( STRINGS::const_iterator it = vfetch.begin(); it!=vfetch.end(); ++it ) + { + // D(printf("findPart:%s\n", it->c_str() );) + (*parts)[*it] = new PART_REVS; + } + } + + // load all the revisions for this part name + PARTS::iterator pi = parts->find( aLPID.GetPartName() ); + + PART_REVS* revs = pi != parts->end() ? pi->second : NULL; + + // D(printf("revs:%p partName:%s\n", revs, aLPID.GetPartName().c_str() );) + + // if the key for parts has no aLPID.GetPartName() the part is not in this lib + if( revs ) + { + if( revs->size() == 0 ) + { + // load all the revisions for this part. + source->GetRevisions( &vfetch, aLPID.GetPartName() ); + + // creat a PART_REV entry for revision, but leave the PART* NULL + for( STRINGS::const_iterator it = vfetch.begin(); it!=vfetch.end(); ++it ) + { + // D(printf("findPartRev:%s\n", it->c_str() );) + (*revs)[*it] = 0; + } + + } + + PART_REVS::iterator result = revs->find( aLPID.GetPartNameAndRev() ); + + if( result != revs->end() ) + { + if( !result->second ) // the PART has never been loaded before + { + result->second = new PART( this, aLPID.GetPartNameAndRev() ); + } + + return result->second; + } + + // If caller did not say what revision, find the highest numbered one and return that. + // Otherwise he knew what he wanted specifically, and we do not have it. + if( !aLPID.GetRevision().size() && revs->size() ) + { + result = revs->begin(); // sort order has highest rev first + + if( !result->second ) // the PART has never been loaded before + { + result->second = new PART( this, LPID::Format( "", aLPID.GetPartName(), result->first ) ); + } + + return result->second; + } + } + + return 0; // no such part name in this lib } PART* LIB::LookupPart( const LPID& aLPID, LIB_TABLE* aLibTable ) throw( IO_ERROR ) { - PART* part; + PART* part = (PART*) findPart( aLPID ); - // If part not already cached - if( 1 /* @todo test cache */ ) + if( !part ) // part does not exist in this lib { - // load it. - - part = new PART( this, aLPID.GetPartName(), aLPID.GetRevision() ); - - std::auto_ptr wrapped( part ); + wxString msg = wxString::Format( _("part '%s' not found in lib %s" ), + wxString::FromUTF8( aLPID.GetPartNameAndRev().c_str() ).GetData(), + wxString::FromUTF8( logicalName.c_str() ).GetData() ); + THROW_IO_ERROR( msg ); + } + if( part->body.empty() ) + { + // load body source->ReadPart( &part->body, aLPID.GetPartName(), aLPID.GetRevision() ); -#if defined(DEBUG) +#if 0 && defined(DEBUG) const STRING& body = part->body; printf( "body: %s", body.c_str() ); if( !body.size() || body[body.size()-1] != '\n' ) @@ -74,12 +224,6 @@ PART* LIB::LookupPart( const LPID& aLPID, LIB_TABLE* aLibTable ) throw( IO_ERROR SWEET_LEXER sw( part->body, wxString::FromUTF8("body") /* @todo have ReadPart give better source */ ); part->Parse( &sw, aLibTable ); - - - // stuff the part into this LIBs cache: - // @todo - - wrapped.release(); } return part; diff --git a/new/sch_lib.h b/new/sch_lib.h index c05bfcf863..12b7cfa14c 100644 --- a/new/sch_lib.h +++ b/new/sch_lib.h @@ -105,7 +105,9 @@ protected: ///< derived classes must implement /** * Function GetRevisions * fetches all revisions for @a aPartName into @a aResults. Revisions are strings - * like "rev12", "rev279", and are library source agnostic. These + * like "rev12", "rev279", and are library source agnostic. These do not have to be + * in a contiguous order, but the first 3 characters must be "rev" and subsequent + * characters must consist of at least one decimal digit. */ virtual void GetRevisions( STRINGS* aResults, const STRING& aPartName ) throw( IO_ERROR ) = 0; @@ -183,6 +185,9 @@ protected: }; +class PARTS; + + /** * Class LIB * is a cache of parts, and because the LIB_SOURCE is abstracted, there @@ -318,17 +323,34 @@ public: protected: - STR_UTF fetch; // scratch, used to fetch things, grows to worst case size. - STR_UTFS vfetch; // scratch, used to fetch things. + STR_UTF fetch; // scratch, used to fetch things, grows to worst case size. + STR_UTFS vfetch; // scratch, used to fetch things. - STRING name; + STRING logicalName; LIB_SOURCE* source; LIB_SINK* sink; -// STRING libraryURI; STRINGS categories; + bool cachedCategories; /// < is true only after reading categories + + + /** parts are in various states of readiness: + * 1) not even loaded (if cachedParts is false) + * 2) present, but without member 'body' having been read() yet. + * 3) body has been read, but not parsed yet. + * 4) parsed and inheritance if any has been applied. + */ + PARTS* parts; + + /** + * Function findPart + * finds a PART, returns NULL if cannot find. + * @throw IO_ERROR if there is some kind of communications error reading + * the original list of parts. + */ + const PART* findPart( const LPID& aLPID ) throw( IO_ERROR ); + -// PARTS parts; }; diff --git a/new/sch_lib_table.h b/new/sch_lib_table.h index 1daa1b418d..a1551b6ad6 100644 --- a/new/sch_lib_table.h +++ b/new/sch_lib_table.h @@ -375,7 +375,6 @@ private: typedef boost::ptr_map ROWS; typedef ROWS::iterator ROWS_ITER; typedef ROWS::const_iterator ROWS_CITER; -// typedef std::pair ROW_PAIR; ROWS rows; LIB_TABLE* fallBack; diff --git a/new/sch_lpid.cpp b/new/sch_lpid.cpp index 4fe1075662..d031dcf877 100644 --- a/new/sch_lpid.cpp +++ b/new/sch_lpid.cpp @@ -186,7 +186,7 @@ LPID::LPID( const STRING& aLPID ) throw( PARSE_ERROR ) { THROW_PARSE_ERROR( _( "Illegal character found in LPID string" ), - wxConvertMB2WX( aLPID.c_str() ), + wxString::FromUTF8( aLPID.c_str() ), aLPID.c_str(), 0, offset @@ -249,6 +249,36 @@ int LPID::SetBaseName( const STRING& aBaseName ) } +int LPID::SetPartName( const STRING& aPartName ) +{ + STRING category; + STRING base; + int offset; + int separation = int( aPartName.find_first_of( "/" ) ); + + if( separation != -1 ) + { + category = aPartName.substr( 0, separation ); + base = aPartName.substr( separation+1 ); + } + else + { + // leave category empty + base = aPartName; + } + + if( (offset = SetCategory( category )) != -1 ) + return offset; + + if( (offset = SetBaseName( base )) != -1 ) + { + return offset + separation + 1; + } + + return -1; +} + + int LPID::SetRevision( const STRING& aRevision ) { int offset = okRevision( aRevision ); @@ -288,6 +318,121 @@ STRING LPID::Format() const } +STRING LPID::GetPartNameAndRev() const +{ + STRING ret; + + if( category.size() ) + { + ret += category; + ret += '/'; + } + + ret += baseName; + + if( revision.size() ) + { + ret += '/'; + ret += revision; + } + + return ret; +} + + +STRING LPID::Format( const STRING& aLogicalLib, const STRING& aPartName, const STRING& aRevision ) + throw( PARSE_ERROR ) +{ + 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 += ':'; + } + + { + STRING category; + STRING base; + + int separation = int( aPartName.find_first_of( "/" ) ); + + if( separation != -1 ) + { + category = aPartName.substr( 0, separation ); + base = aPartName.substr( separation+1 ); + } + else + { + // leave category empty + base = aPartName; + } + + if( (offset = okCategory( category )) != -1 ) + { + THROW_PARSE_ERROR( + _( "Illegal character found in category" ), + wxString::FromUTF8( aRevision.c_str() ), + aRevision.c_str(), + 0, + offset + ); + } + + 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 + ); + } + + if( category.size() ) + { + ret += category; + ret += '/'; + } + + 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 diff --git a/new/sch_lpid.h b/new/sch_lpid.h index 82694edb74..3a20e28c59 100644 --- a/new/sch_lpid.h +++ b/new/sch_lpid.h @@ -145,6 +145,23 @@ public: return partName; } + /** + * Function GetPartNameAndRev + * returns the part name with revision if any, i.e. [category/]baseName[/revN..] + */ + STRING GetPartNameAndRev() const; + + /** + * Function SetPartName + * overrides the part name portion of the LPID to @a aPartName + * @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 SetPartName( const STRING& aPartName ); + /** * Function GetRevision * returns the revision portion of the LPID. @@ -170,6 +187,16 @@ public: */ STRING Format() const; + /** + * Function Format + * returns a STRING in the proper format as an LPID for a combination of + * aLogicalLib, aPartName, and aRevision. + * @throw PARSE_ERROR if any of the pieces are illegal. + */ + static STRING Format( const STRING& aLogicalLib, const STRING& aPartName, const STRING& aRevision="" ) + throw( PARSE_ERROR ); + + #if defined(DEBUG) static void Test(); #endif diff --git a/new/sch_part.cpp b/new/sch_part.cpp index a4196d8344..bf9073c022 100644 --- a/new/sch_part.cpp +++ b/new/sch_part.cpp @@ -31,9 +31,13 @@ using namespace SCH; -//----------------------- +#define MAX_INHERITANCE_NESTING 10 // no problem going larger +//----------------------- +struct XY {}; +struct AT {}; + //---------------------- @@ -55,7 +59,8 @@ class PART_PARSER { SWEET_LEXER* in; LIB_TABLE* libs; - int contains; + int contains; // separate from PART::contains until done + // so we can see what we inherited from base PART public: PART_PARSER( PART* aPart, SWEET_LEXER* aLexer, LIB_TABLE* aTable ) : @@ -66,51 +71,98 @@ public: parsePart( aPart ); } + + void parseXY( XY* me ) + { + } + + void parseAt( AT* me ) + { + } + + + void parseExtends( PART* me ) + { + PART* base; + int offset; + + if( contains & PB(EXTENDS) ) + in->Duplicate( T_extends ); + + in->NeedSYMBOLorNUMBER(); + me->setExtends( new LPID() ); + + offset = me->extends->Parse( in->CurText() ); + if( offset > -1 ) // -1 is success + THROW_PARSE_ERROR( _("invalid extends LPID"), + in->CurSource(), + in->CurLine(), + in->CurLineNumber(), + in->CurOffset() + offset ); + + base = libs->LookupPart( *me->extends, me->Owner() ); + + // we could be going in circles here, recursively, or too deep, set limits + // and disallow extending from self (even indirectly) + int extendsDepth = 0; + for( PART* ancestor = base; ancestor && extendsDepthbase ) + { + if( ancestor == me ) + { + THROW_PARSE_ERROR( _("'extends' may not have self as any ancestor"), + in->CurSource(), + in->CurLine(), + in->CurLineNumber(), + in->CurOffset() ); + } + } + + if( extendsDepth == MAX_INHERITANCE_NESTING ) + { + THROW_PARSE_ERROR( _("max allowed extends depth exceeded"), + in->CurSource(), + in->CurLine(), + in->CurLineNumber(), + in->CurOffset() ); + } + + me->inherit( *base ); + me->base = base; + contains |= PB(EXTENDS); + } + /// @param me = ja mir, the object getting stuffed, from its perspective void parsePart( PART* me ) { - PART_T tok; + PART_T tok = in->NextTok(); - if( (tok = in->NextTok()) == T_LEFT ) - tok = in->NextTok(); - - // a token "( part .." i.e. class PART // Be flexible regarding the starting point of the stream. // Caller may not have read the first two tokens out of the // stream: T_LEFT and T_part, so ignore them if seen here. // The 1st two tokens T_LEFT and T_part are then optional in the grammar. - if( tok == T_part ) + + if( tok == T_LEFT ) { - in->NeedSYMBOLorNUMBER(); // read in part NAME_HINT, and toss - tok = in->NextTok(); + if( ( tok = in->NextTok() ) != T_part ) + in->Expecting( T_part ); } + in->NeedSYMBOLorNUMBER(); // read in part NAME_HINT, and toss + tok = in->NextTok(); + // extends must be _first_ thing, if it is present at all, after NAME_HINT if( tok == T_extends ) { - PART* base; - int offset; - - if( contains & PB(EXTENDS) ) - in->Duplicate( tok ); - in->NeedSYMBOLorNUMBER(); - me->setExtends( new LPID() ); - offset = me->extends->Parse( in->CurText() ); - if( offset > -1 ) // -1 is success - THROW_PARSE_ERROR( _("invalid extends LPID"), - in->CurSource(), - in->CurLine(), - in->CurLineNumber(), - in->CurOffset() + offset ); - // we could be going in circles here, recursively, @todo add a max counter or stack chain - base = libs->LookupPart( *me->extends, me->Owner() ); - me->inherit( *base ); - contains |= PB(EXTENDS); + parseExtends( me ); tok = in->NextTok(); } - for( ; tok!=T_RIGHT && tok!=T_EOF; tok = in->NextTok() ) + for( ; tok!=T_RIGHT; tok = in->NextTok() ) { + if( tok==T_EOF ) + in->Unexpected( _( "end of input" ) ); + if( tok == T_LEFT ) tok = in->NextTok(); @@ -212,22 +264,26 @@ public: break; } } + + contains |= PB(PARSED); + + this->contains |= contains; } - - void parseAt( PART* me ) - { - } }; -PART::PART( LIB* aOwner, const STRING& aPartName, const STRING& aRevision ) : +PART::PART( LIB* aOwner, const STRING& aPartNameAndRev ) : owner( aOwner ), contains( 0 ), - partName( aPartName ), - revision( aRevision ), - extends( 0 ) -{} + partNameAndRev( aPartNameAndRev ), + extends( 0 ), + base( 0 ) +{ + // Our goal is to have class LIB only instantiate what is needed, so print here + // what it is doing. It is the only class where PART can be instantiated. + D(printf("PART::PART(%s)\n", aPartNameAndRev.c_str() );) +} PART::~PART() @@ -253,10 +309,10 @@ void PART::inherit( const PART& other ) PART& PART::operator=( const PART& other ) { - owner = other.owner; - partName = other.partName; - revision = other.revision; - body = other.body; + owner = other.owner; + partNameAndRev = other.partNameAndRev; + body = other.body; + base = other.base; setExtends( other.extends ? new LPID( *other.extends ) : 0 ); diff --git a/new/sch_part.h b/new/sch_part.h index fadee34be7..85319e875a 100644 --- a/new/sch_part.h +++ b/new/sch_part.h @@ -43,9 +43,8 @@ class LPID; */ enum PartBit { - BODY, ///< body has been read in. PARSED, ///< have parsed this part already, otherwise 'body' text must be parsed - EXTENDS, ///< saw and "extends" keyword, inheriting from another PART + EXTENDS, ///< saw "extends" keyword, inheriting from another PART VALUE, ANCHOR, REFERENCE, @@ -55,6 +54,7 @@ enum PartBit KEYWORDS, }; + /// Function PB /// is a PartBit shifter for PART::contains field. static inline const int PB( PartBit oneBitOnly ) @@ -80,7 +80,7 @@ class PART protected: // not likely to have C++ descendants, but protected none-the-less. /// a protected constructor, only a LIB can instantiate a PART. - PART( LIB* aOwner, const STRING& aPartName, const STRING& aRevision ); + PART( LIB* aOwner, const STRING& aPartNameAndRev ); /** * Function inherit @@ -95,10 +95,10 @@ protected: // not likely to have C++ descendants, but protected none-the-le LIB* owner; ///< which LIB am I a part of (pun if you want) int contains; ///< has bits from Enum PartParts - STRING partName; ///< example "passives/R", immutable. - STRING revision; // @todo need a single search key, this won't do. + STRING partNameAndRev; ///< example "passives/R[/revN..]", immutable. LPID* extends; ///< of base part, NULL if none, otherwise I own it. + PART* base; ///< which PART am I extending, if any. no ownership. /// encapsulate the old version deletion, take ownership of @a aLPID void setExtends( LPID* aLPID ); @@ -107,6 +107,7 @@ protected: // not likely to have C++ descendants, but protected none-the-le /// actually becomes cached in RAM. STRING body; +// bool cachedRevisions; ///< allows lazy loading of revision of this same part name // 3 separate lists for speed: @@ -136,7 +137,6 @@ public: */ LIB* Owner() { return owner; } - /** * Function Parse * translates a Sweet string into a binary form that is represented