From 84ed5f501da4e6500f6cf895bd7bdc96ab846914 Mon Sep 17 00:00:00 2001 From: Dick Hollenbeck Date: Sat, 1 Jan 2011 16:56:07 -0600 Subject: [PATCH] ++new: * Added the basic structure to the Sweet parser in sch_part.cpp. * Got inheritence working off of the 'extends' keyword and PART::inherit() * Tossed the units support out of sweet.keywords, since we agreed to go dimensionless. ++richio: * Added the problemInputLine support to PARSE_ERROR, so UI can show the offending line of bytes. Yes bytes, not even guaranteed to be characters. --- CHANGELOG.txt | 11 ++ common/dsnlexer.cpp | 38 ++--- include/dsnlexer.h | 39 ++++- include/richio.h | 40 +++-- new/make-dir-lib-source-test-data.sh | 3 +- new/sch_lib_table.cpp | 6 + new/sch_lpid.cpp | 1 + new/sch_lpid.h | 2 +- new/sch_part.cpp | 247 ++++++++++++++++++++++++++- new/sch_part.h | 63 ++++--- new/sweet.keywords | 3 + pcbnew/specctra_export.cpp | 2 +- pcbnew/specctra_import.cpp | 2 +- 13 files changed, 381 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 19df7396dc..64890c564b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,17 @@ KiCad ChangeLog 2010 Please add newer entries at the top, list the date and your name with email address. +2011-Jan-1 UPDATE Dick Hollenbeck +================================================================================ +++new: + * Added the basic structure to the Sweet parser in sch_part.cpp. + * Got inheritence working off of the 'extends' keyword and PART::inherit() + * Tossed the units support out of sweet.keywords, since we agreed to go dimensionless. +++richio: + * Added the problemInputLine support to PARSE_ERROR, so UI can show the + offending line of bytes. Yes bytes, not even guaranteed to be characters. + + 2010-dec-31 UPDATE Wayne Stambaugh ================================================================================ ++all diff --git a/common/dsnlexer.cpp b/common/dsnlexer.cpp index b1ded2154e..45c059d1c8 100644 --- a/common/dsnlexer.cpp +++ b/common/dsnlexer.cpp @@ -231,7 +231,7 @@ wxString DSNLEXER::GetTokenString( int aTok ) { wxString ret; - ret << wxT("'") << wxConvertMB2WX( GetTokenText(aTok) ) << wxT("'"); + ret << wxT("'") << wxString::FromUTF8( GetTokenText(aTok) ) << wxT("'"); return ret; } @@ -249,24 +249,11 @@ bool DSNLEXER::IsSymbol( int aTok ) } -void DSNLEXER::ThrowIOError( wxString aText, int charOffset ) throw( IO_ERROR ) -{ - // @todo convert this to THROW_PARSE_ERROR() - - // append to aText, do not overwrite - aText << wxT(" ") << _("in") << wxT(" \"") << CurSource() - << wxT("\" ") << _("on line") << wxT(" ") << reader->LineNumber() - << wxT(" ") << _("at offset") << wxT(" ") << charOffset; - - THROW_IO_ERROR( aText ); -} - - void DSNLEXER::Expecting( int aTok ) throw( IO_ERROR ) { wxString errText( _("Expecting") ); errText << wxT(" ") << GetTokenString( aTok ); - ThrowIOError( errText, CurOffset() ); + THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } @@ -274,7 +261,7 @@ void DSNLEXER::Expecting( const wxString& text ) throw( IO_ERROR ) { wxString errText( _("Expecting") ); errText << wxT(" '") << text << wxT("'"); - ThrowIOError( errText, CurOffset() ); + THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } @@ -282,15 +269,22 @@ void DSNLEXER::Unexpected( int aTok ) throw( IO_ERROR ) { wxString errText( _("Unexpected") ); errText << wxT(" ") << GetTokenString( aTok ); - ThrowIOError( errText, CurOffset() ); + THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } +void DSNLEXER::Duplicate( int aTok ) throw( IO_ERROR ) +{ + wxString errText; + + errText.Printf( _("%s is a duplicate"), GetTokenString( aTok ).GetData() ); + THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); +} void DSNLEXER::Unexpected( const wxString& text ) throw( IO_ERROR ) { wxString errText( _("Unexpected") ); errText << wxT(" '") << text << wxT("'"); - ThrowIOError( errText, CurOffset() ); + THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } @@ -408,7 +402,7 @@ L_read: case '"': break; default: - ThrowIOError( errtxt, CurOffset() ); + THROW_PARSE_ERROR( errtxt, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } curText = cc; @@ -417,7 +411,7 @@ L_read: if( head= limit ) { wxString errtxt(_("Un-terminated delimited string") ); - ThrowIOError( errtxt, CurOffset() ); + THROW_PARSE_ERROR( errtxt, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } curText.clear(); diff --git a/include/dsnlexer.h b/include/dsnlexer.h index 232b386c0f..a5650d9d2a 100644 --- a/include/dsnlexer.h +++ b/include/dsnlexer.h @@ -330,13 +330,6 @@ public: */ static bool IsSymbol( int aTok ); - /** - * Function ThrowIOError - * encapsulates the formatting of an error message which contains the exact - * location within the input file of something the caller is rejecting. - */ - void ThrowIOError( wxString aText, int charOffset ) throw( IO_ERROR ); - /** * Function Expecting * throws an IO_ERROR exception with an input file specific error message. @@ -363,6 +356,16 @@ public: */ void Unexpected( int aTok ) throw( IO_ERROR ); + /** + * Function Duplicate + * throws an IO_ERROR exception with a message saying specifically that aTok + * is a duplicate of one already seen in current context. + * @param aTok is the token/keyword type which was not expected at the + * current input location. + * @throw IO_ERROR with the location within the input file of the problem. + */ + void Duplicate( int aTok ) throw( IO_ERROR ); + /** * Function Unexpected * throws an IO_ERROR exception with an input file specific error message. @@ -411,6 +414,16 @@ public: return curText.c_str(); } + /** + * Function FromUTF8 + * returns the current token text as a wxString, assuming that the input + * byte stream is UTF8 encoded. + */ + wxString FromUTF8() + { + return wxString::FromUTF8( curText.c_str() ); + } + /** * Function CurLineNumber * returns the current line number within my LINE_READER @@ -420,6 +433,16 @@ public: return reader->LineNumber(); } + /** + * Function CurLine + * returns the current line of text, from which the CurText() would return + * its token. + */ + const char* CurLine() + { + return (const char*)(*reader); + } + /** * Function CurFilename * returns the current LINE_READER source. @@ -433,7 +456,7 @@ public: /** * Function CurOffset - * returns the char offset within the current line, using a 1 based index. + * returns the byte offset within the current line, using a 1 based index. * @return int - a one based index into the current line. */ int CurOffset() diff --git a/include/richio.h b/include/richio.h index ce40141a20..9e8362d41e 100644 --- a/include/richio.h +++ b/include/richio.h @@ -46,8 +46,8 @@ */ -#define IO_FORMAT _( "IO_ERROR: '%s'\n from %s : %s" ) -#define PARSE_FORMAT _( "PARSE_ERROR: '%s' in input/source '%s', line %d, offset %d\n from %s : %s" ) +#define IO_FORMAT _( "IO_ERROR: %s\n from %s : %s" ) +#define PARSE_FORMAT _( "PARSE_ERROR: %s in input/source \"%s\", line %d, offset %d\n from %s : %s" ) // references: // http://stackoverflow.com/questions/2670816/how-can-i-use-the-compile-time-constant-line-in-a-string @@ -123,39 +123,50 @@ struct IO_ERROR // : std::exception }; -#define THROW_PARSE_ERROR( msg, input, line, offset ) throw PARSE_ERROR( __FILE__, __LOC__, msg, input, line, offset ) - /** * Class PARSE_ERROR - * contains a filename or source description, a line number, a character offset, - * and an error message. + * contains a filename or source description, a problem input line, a line number, + * a byte offset, and an error message which contains the the caller's report and his + * call site information: CPP source file, function, and line number. * @author Dick Hollenbeck */ struct PARSE_ERROR : public IO_ERROR { // wxString errorText is still public from IO_ERROR - int lineNumber; ///< at which line number, 1 based index. - int byteIndex; ///< at which character position within the line, 1 based index + int lineNumber; ///< at which line number, 1 based index. + int byteIndex; ///< at which character position within the line, 1 based index + /// problem line of input [say, from a LINE_READER]. + /// this is brought up in original byte format rather than wxString form, incase + /// there was a problem with the encoding, in which case converting to wxString is + /// not reliable in this context. + std::string inputLine; + + /** + * Constructor + * which is normally called via the macro THROW_PARSE_ERROR so that + * __FILE__ and __LOC__ can be captured from the call site. + */ PARSE_ERROR( const char* aThrowersFile, const char* aThrowersLoc, const wxString& aMsg, const wxString& aSource, + const char* aInputLine, int aLineNumber, int aByteIndex ) : IO_ERROR() { - init( aThrowersFile, aThrowersLoc, aMsg, aSource, aLineNumber, aByteIndex ); + init( aThrowersFile, aThrowersLoc, aMsg, aSource, aInputLine, aLineNumber, aByteIndex ); } void init( const char* aThrowersFile, const char* aThrowersLoc, const wxString& aMsg, const wxString& aSource, + const char* aInputLine, int aLineNumber, int aByteIndex ) { - // save line and offset in binary for Sweet text editor, which will catch exceptions + // save inpuLine, lineNumber, and offset for UI (.e.g. Sweet text editor) + inputLine = aInputLine; lineNumber = aLineNumber; byteIndex = aByteIndex; - // #define PARSE_FORMAT _( "PARSE_ERROR: %s in source %s, line %d, offset %d\nfrom cpp:%s func:%s" ) - errorText.Printf( PARSE_FORMAT, aMsg.GetData(), aSource.GetData(), aLineNumber, aByteIndex, wxString::FromUTF8( aThrowersFile ).GetData(), @@ -165,6 +176,11 @@ struct PARSE_ERROR : public IO_ERROR ~PARSE_ERROR() throw ( /*none*/ ){} }; + +#define THROW_PARSE_ERROR( aMsg, aSource, aInputLine, aLineNumber, aByteIndex ) \ + throw PARSE_ERROR( __FILE__, __LOC__, aMsg, aSource, aInputLine, aLineNumber, aByteIndex ) + + /** @} exception_types */ diff --git a/new/make-dir-lib-source-test-data.sh b/new/make-dir-lib-source-test-data.sh index 54bd70bbea..1e6ab0face 100755 --- a/new/make-dir-lib-source-test-data.sh +++ b/new/make-dir-lib-source-test-data.sh @@ -16,8 +16,7 @@ for C in ${CATEGORIES}; do for P in ${PARTS}; do for R in ${REVS}; do - echo "#$R: (part $C/$P)" > $BASEDIR/$C/$P.part.$R + (part $C/$P/$R extends $P/$R (value 22)(footprint SM0805)) > $BASEDIR/$C/$P.part.$R done done done - diff --git a/new/sch_lib_table.cpp b/new/sch_lib_table.cpp index a5100c5d55..42d13ca31d 100644 --- a/new/sch_lib_table.cpp +++ b/new/sch_lib_table.cpp @@ -413,6 +413,12 @@ int main( int argc, char** argv ) lib_table.Test(); } + catch( PARSE_ERROR& ioe ) // most derived class first + { + printf( "%s\n", (const char*) ioe.errorText.ToUTF8() ); + printf( "%s", ioe.inputLine.c_str() ); // rare not to have \n at end + printf( "%*s^\n", ioe.byteIndex-1, " " ); + } catch( IO_ERROR& ioe ) { printf( "%s\n", (const char*) ioe.errorText.ToUTF8() ); diff --git a/new/sch_lpid.cpp b/new/sch_lpid.cpp index d80fb61a66..4fe1075662 100644 --- a/new/sch_lpid.cpp +++ b/new/sch_lpid.cpp @@ -187,6 +187,7 @@ LPID::LPID( const STRING& aLPID ) throw( PARSE_ERROR ) THROW_PARSE_ERROR( _( "Illegal character found in LPID string" ), wxConvertMB2WX( aLPID.c_str() ), + aLPID.c_str(), 0, offset ); diff --git a/new/sch_lpid.h b/new/sch_lpid.h index 44d08a17a4..82694edb74 100644 --- a/new/sch_lpid.h +++ b/new/sch_lpid.h @@ -59,7 +59,7 @@ class LPID // aka GUID { public: - LPID(); + LPID() {} /** * Constructor LPID diff --git a/new/sch_part.cpp b/new/sch_part.cpp index ad88353adf..b1f119a6e6 100644 --- a/new/sch_part.cpp +++ b/new/sch_part.cpp @@ -21,22 +21,259 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include // _() #include #include -#include // _() - +#include +#include using namespace SCH; -void PART::Parse( SWEET_LEXER* aLexer, LIB_TABLE* aTable ) throw( PARSE_ERROR ) -{ - // Wayne's world, if he still wants it. +//----------------------- + + +//---------------------- + + +/** + * Class PART_PARSER + * is a localized/hidden PART Parser. You get here through the public interface + * PART::Parse(). Advantages of private class declaration in this situation: + * 1) keeps all the recursive parsing helper functions out of the main public PART +* header file and so should speed up compilation. + * 2) Allows use of cost-less Java like inline functions, since nobody knows about + * them but this source file. Most are only used once and called from one place. + *

+ * All the functions in this class throw PARSE_ERROR. If SWEET_LEXER throws, it + * may be an IO_ERROR, propogated from here also. The throws() statements are left off + * to keep the noise level down. + */ +class PART_PARSER +{ + SWEET_LEXER* in; + LIB_TABLE* libs; + int contains; + +public: + PART_PARSER( PART* aPart, SWEET_LEXER* aLexer, LIB_TABLE* aTable ) : + in( aLexer ), + libs( aTable ), + contains( 0 ) + { + parsePart( aPart ); + } + + /// @param me = ja mir, the object getting stuffed, from its perspective + void parsePart( PART* me ) + { + PART_T tok; + + 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 ) + { + 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); + tok = in->NextTok(); + } + + for( ; tok!=T_RIGHT && tok!=T_EOF; tok = in->NextTok() ) + { + if( tok == T_LEFT ) + tok = in->NextTok(); + + switch( tok ) + { + default: + // describe what we expect at this level + in->Expecting( wxT( + "anchor|value|footprint|model|keywords|alternates\n" + "|property\n" + " |property_del\n" + "|pin\n" + " |pin_merge|pin_swap|pin_renum|pin_rename|route_pin_swap\n" + "|polyline|line|rectangle|circle|arc|bezier|text" + ) + ); + break; + + case T_anchor: + if( contains & PB(ANCHOR) ) + in->Duplicate( tok ); + contains |= PB(ANCHOR); + break; + + /* + case T_value: + if( contains & PB(VALUE) ) + in->Duplicate( tok ); + contains |= PB(VALUE); + in->NeedSYMBOLorNUMBER(); + // me->value = in->CurText(); + in->NeedRIGHT(); + break; + + case T_footprint: + break; + + case T_model: + break; + + case T_keywords: + break; + + case T_alternates: + break; + + case T_property: + break; + + case T_property_del: + break; + + case T_pin: + break; + + case T_pin_merge: + break; + + case T_pin_swap: + break; + + case T_pin_renum: + break; + + case T_pin_rename: + break; + + case T_route_pin_swap: + break; + + case T_polyline: + break; + + case T_line: + break; + + case T_rectangle: + break; + + case T_circle: + break; + + case T_arc: + break; + + case T_bezier: + break; + + case T_text: + break; + */ + + // Not sure about reference in a PART, comes in at COMPONENT object. + // It is maybe just a hint here or a prefix. + case T_reference: + if( contains & PB(REFERENCE) ) + in->Duplicate( tok ); + contains |= PB(REFERENCE); + break; + } + } + } + + + void parseAt( PART* me ) + { + } +}; + + +PART::PART( LIB* aOwner, const STRING& aPartName, const STRING& aRevision ) : + owner( aOwner ), + contains( 0 ), + partName( aPartName ), + revision( aRevision ), + extends( 0 ) +{} + + +PART::~PART() +{ + delete extends; } +void PART::setExtends( LPID* aLPID ) +{ + delete extends; + extends = aLPID; +} + + +void PART::inherit( const PART& other ) +{ + contains = other.contains; + + setExtends( other.extends ? new LPID( *other.extends ) : 0 ); + + body = other.body; +} + + +PART& PART::operator=( const PART& other ) +{ + owner = other.owner; + partName = other.partName; + revision = other.revision; + + // maintain inherit() as a partial assignment operator. + inherit( other ); + + return *this; +} + + +void PART::Parse( SWEET_LEXER* aLexer, LIB_TABLE* aTable ) throw( IO_ERROR ) +{ + PART_PARSER( this, aLexer, aTable ); +} + + + + #if 0 && defined(DEBUG) int main( int argc, char** argv ) diff --git a/new/sch_part.h b/new/sch_part.h index 7cafcaf819..fadee34be7 100644 --- a/new/sch_part.h +++ b/new/sch_part.h @@ -28,9 +28,13 @@ #include class SWEET_LEXER; +class PART_PARSER; + namespace SCH { +class LPID; + /** * Enum PartBit * is a set of bit positions that can be used to create flag bits within @@ -39,9 +43,16 @@ namespace SCH { */ 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 - + VALUE, + ANCHOR, + REFERENCE, + FOOTPRINT, + DATASHEET, + MODEL, + KEYWORDS, }; /// Function PB @@ -63,34 +74,40 @@ static inline const int PB( PartBit oneBitOnly ) */ class PART { - /// LIB class has great license to modify what's in here, nobody else does. - /// Modification is done through the LIB so it can track the state of the - /// PART and take action as needed. Actually most of the modification will - /// be done by PARTS_LIST, a class derived from LIB. - friend class LIB; - - /// a private constructor, only a LIB can instantiate a PART. - PART( LIB* aOwner, const STRING& aPartName, const STRING& aRevision ) : - owner( aOwner ), - contains( 0 ), - partName( aPartName ), - revision( aRevision ) - {} + friend class LIB; // is the owner of all PARTS, afterall + friend class ::PART_PARSER; 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 ); + + /** + * Function inherit + * is a specialized assignment function that copies a specific subset, enough + * to fulfill the requirements of the Sweet s-expression language. + */ + void inherit( const PART& aBasePart ); + + + //PART( LIB* aOwner ); + LIB* owner; ///< which LIB am I a part of (pun if you want) int contains; ///< has bits from Enum PartParts - STRING extends; ///< LPID of base part - STRING partName; ///< example "passives/R", immutable. STRING revision; // @todo need a single search key, this won't do. + LPID* extends; ///< of base part, NULL if none, otherwise I own it. + + /// encapsulate the old version deletion, take ownership of @a aLPID + void setExtends( LPID* aLPID ); + /// s-expression text for the part, initially empty, and read in as this part /// actually becomes cached in RAM. STRING body; + // 3 separate lists for speed: /// A property list. @@ -105,16 +122,13 @@ protected: // not likely to have C++ descendants, but protected none-the-le /// Alternate body forms. //ALTERNATES alternates; - // lots of other stuff, like the mandatory properties. + // lots of other stuff, like the mandatory properties, but no units, since we went with dimensionless public: - /** - * Function Inherit - * is a specialized assignment function that copies a specific subset, enough - * to fulfill the requirements of the Sweet s-expression language. - */ - void Inherit( const PART& aBasePart ); + ~PART(); + + PART& operator=( const PART& other ); /** * Function Owner @@ -122,6 +136,7 @@ public: */ LIB* Owner() { return owner; } + /** * Function Parse * translates a Sweet string into a binary form that is represented @@ -133,7 +148,7 @@ public: * @param aLibTable is the LIB_TABLE view that is in effect for inheritance, * and comes from the big containing SCHEMATIC object. */ - void Parse( SWEET_LEXER* aLexer, LIB_TABLE* aTable ) throw( PARSE_ERROR ); + void Parse( SWEET_LEXER* aLexer, LIB_TABLE* aTable ) throw( IO_ERROR ); /* void SetBody( const STR_UTF& aSExpression ) diff --git a/new/sweet.keywords b/new/sweet.keywords index 0855b8ea6d..60e4e387c8 100644 --- a/new/sweet.keywords +++ b/new/sweet.keywords @@ -25,6 +25,7 @@ keywords length line line_width +model name none number @@ -38,6 +39,7 @@ pin_del pin_merge pin_rename pin_renum +pin_swap polyline pos power_in @@ -58,5 +60,6 @@ tristate unconnected units unspecified +value visible xy diff --git a/pcbnew/specctra_export.cpp b/pcbnew/specctra_export.cpp index b46c717562..edf50b46d2 100644 --- a/pcbnew/specctra_export.cpp +++ b/pcbnew/specctra_export.cpp @@ -111,7 +111,7 @@ void WinEDA_PcbFrame::ExportToSpecctra( wxCommandEvent& event ) // if an exception is thrown by FromBOARD or ExportPCB(), then // ~SPECCTRA_DB() will close the file. } - catch( IO_ERROR ioe ) + catch( IO_ERROR& ioe ) { ok = false; diff --git a/pcbnew/specctra_import.cpp b/pcbnew/specctra_import.cpp index c584c49c33..f6edb00c2c 100644 --- a/pcbnew/specctra_import.cpp +++ b/pcbnew/specctra_import.cpp @@ -96,7 +96,7 @@ void WinEDA_PcbFrame::ImportSpecctraSession( wxCommandEvent& event ) db.LoadSESSION( fullFileName ); db.FromSESSION( GetBoard() ); } - catch( IO_ERROR ioe ) + catch( IO_ERROR& ioe ) { SetLocaleTo_Default( ); // revert to the current locale