Implement "KiCad s-epression" support for boost::property_tree's ptree, the 8

bit string version of property_tree. The ram resident structure of the ptree is
mostly compatible with one created using the xml_parser from
boost::property_tree, with slight differences in the way atoms are stored. The
result is you can use Format() to convert from xml to s-expression, but not the
other way around. You can write a simple s-expression beautifier in just a few
lines of code.

The main value however is the s-expression parser, i.e. Scan(), which is an
alternative to crafting a custom recursive descent parser for a particular
grammar. The tipping point depends on whether you want to read only a small
portion of a much larger document. If so, then using the ptree will likely be a
"faster to code" route. Documentation on how to navigate a ptree can be found on
the boost website and there are a number of examples in the
pcbnew/eagle_plugin.cpp file in this project. Powerful path navigation support
makes it easy to extract a subset of a ptree.
This commit is contained in:
Dick Hollenbeck 2013-04-08 16:04:45 -05:00
parent 7bb04c8f63
commit 6bf3d7cdc3
6 changed files with 418 additions and 293 deletions

View File

@ -71,6 +71,7 @@ set(COMMON_SRCS
netlist_keywords.cpp netlist_keywords.cpp
newstroke_font.cpp newstroke_font.cpp
projet_config.cpp projet_config.cpp
ptree.cpp
richio.cpp richio.cpp
selcolor.cpp selcolor.cpp
string.cpp string.cpp

226
common/ptree.cpp Normal file
View File

@ -0,0 +1,226 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2013 KiCad Developers, see CHANGELOG.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 <boost/property_tree/ptree.hpp>
#include <assert.h>
#include <macros.h>
#include <ptree.h>
typedef PTREE::const_iterator CITER;
typedef PTREE::iterator ITER;
#if defined(DEBUG)
#define D(x) x
#else
#define D(x)
#endif
#define CTL_OMIT_NL (1<<0)
#define CTL_IN_ATTRS (1<<1)
//-----<Scan>------------------------------------------------------------------
/**
* Function scanList
* reads a sexpr list from the input stream into a new node with key
* aLexer->CurText().
*/
inline void scanList( PTREE* aTree, DSNLEXER* aLexer )
{
assert( aLexer->CurTok() == DSN_LEFT );
int tok = aLexer->NextTok();
const char* key = aLexer->CurText();
//D(printf( "%s: '%s'\n", __func__, key );)
PTREE* list = &aTree->push_back( PTREE::value_type( key, PTREE() ) )->second;
if( tok != DSN_RIGHT )
{
while( ( tok = aLexer->NextTok() ) != DSN_RIGHT )
{
if( tok == DSN_EOF )
aLexer->Unexpected( DSN_EOF );
Scan( list, aLexer );
}
}
}
inline void scanAtom( PTREE* aTree, DSNLEXER* aLexer )
{
const char* key = aLexer->CurText();
//D(printf( "%s: '%s'\n", __func__, key );)
aTree->push_back( PTREE::value_type( key, PTREE() ) );
}
void Scan( PTREE* aTree, DSNLEXER* aLexer ) throw( IO_ERROR )
{
int tok = aLexer->CurTok();
// conditionally read first token.
if( tok == DSN_NONE )
tok = aLexer->NextTok();
if( tok == DSN_EOF )
{
aLexer->Unexpected( DSN_EOF );
}
if( tok == DSN_LEFT )
{
scanList( aTree, aLexer );
}
else
{
scanAtom( aTree, aLexer );
}
}
//-----<Format>------------------------------------------------------------------
inline bool isAtom( CPTREE& aTree )
{
return aTree.size()==0 && aTree.data().size()==0;
}
inline bool isLast( CPTREE& aTree, CITER it )
{
CITER next = it;
++next;
return next == aTree.end();
}
inline CITER next( CITER it )
{
CITER n = it;
return ++n;
}
static void formatNode( OUTPUTFORMATTER* out, int aNestLevel, int aCtl,
const std::string& aKey, CPTREE& aTree ) throw( IO_ERROR );
static void formatList( OUTPUTFORMATTER* out, int aNestLevel, int aCtl, CPTREE& aTree )
throw( IO_ERROR )
{
for( CITER it = aTree.begin(); it != aTree.end(); ++it )
{
// Processing a tree which was read in with xml_parser?
if( it->first == "<xmlattr>" )
{
formatList( out, aNestLevel, aCtl | CTL_IN_ATTRS, it->second );
continue;
}
int ctl = 0;
#if defined(DEBUG)
if( it->first == "field" )
{
int breakhere = 1;
(void) breakhere;
}
#endif
if( isLast( aTree, it ) ) // is "it" the last one?
{
//if( !( aCtl & CTL_IN_ATTRS ) )
ctl = CTL_OMIT_NL;
}
else if( isAtom( next( it )->second ) )
{
/* if( !( aCtl & CTL_IN_ATTRS ) ) */
ctl = CTL_OMIT_NL;
}
formatNode( out, aNestLevel+1, ctl, it->first, it->second );
}
}
static void formatNode( OUTPUTFORMATTER* out, int aNestLevel, int aCtl,
const std::string& aKey, CPTREE& aTree )
throw( IO_ERROR )
{
if( !isAtom( aTree ) ) // is a list, not an atom
{
int ctl = CTL_OMIT_NL;
// aTree is list and its first child is a list
if( aTree.size() && !isAtom( aTree.begin()->second ) && !aTree.data().size() )
ctl = 0;
out->Print( aNestLevel, "(%s%s", out->Quotes( aKey ).c_str(), ctl & CTL_OMIT_NL ? "" : "\n" );
if( aTree.data().size() ) // only xml typically uses "data()", not sexpr.
{
out->Print( 0, " %s%s",
out->Quotes( aTree.data() ).c_str(),
aTree.size() ? "\n" : ""
);
}
formatList( out, aNestLevel, aCtl, aTree );
out->Print( 0, ")%s", aCtl & CTL_OMIT_NL ? "" : "\n" );
}
else // is an atom, not a list
{
const char* atom = out->Quotes( aKey ).c_str();
out->Print( 0, " %s", atom );
}
}
void Format( OUTPUTFORMATTER* out, int aNestLevel, int aCtl, CPTREE& aTree ) throw( IO_ERROR )
{
if( aTree.size() == 1 && !aTree.data().size() )
{
// The topmost node is basically only a container for the document root.
// It anchors the paths which traverse the tree deeper.
CITER it = aTree.begin();
formatNode( out, aNestLevel, aCtl, it->first, it->second );
}
else
{
// This is not expected, neither for sexpr nor xml.
formatNode( out, aNestLevel, aCtl, "", aTree );
}
}

96
include/ptree.h Normal file
View File

@ -0,0 +1,96 @@
#ifndef PTREE_H_
#define PTREE_H_
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2013 KiCad Developers, see CHANGELOG.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
*/
/*
Implement "KiCad s-epression" support for boost::property_tree's ptree, the 8
bit string version of property_tree. The ram resident structure of the ptree is
mostly compatible with one created using the xml_parser from
boost::property_tree, with slight differences in the way atoms are stored. The
result is you can use Format() to convert from xml to s-expression, but not the
other way around. You can write a simple s-expression beautifier in just a few
lines of code.
The main value however is the s-expression parser, i.e. Scan(), which is an
alternative to crafting a custom recursive descent parser for a particular
grammar. The tipping point depends on whether you want to read only a small
portion of a much larger document. If so, then using the ptree will likely be a
"faster to code" route. Documentation on how to navigate a ptree can be found on
the boost website and there are a number of examples in the
pcbnew/eagle_plugin.cpp file in this project. Powerful path navigation support
makes it easy to extract a subset of a ptree.
*/
#include <boost/property_tree/ptree_fwd.hpp>
#include <richio.h>
#include <dsnlexer.h>
typedef boost::property_tree::ptree PTREE;
typedef const PTREE CPTREE;
/**
* Function Scan
* fills an empty PTREE with information from a KiCad s-expresion stream. Use
* a DSNLEXER with an empty keyword table as @a aLexer. Useful for parsing
* s-expression files or strings of arbitrary grammars, say from a file or clipboard.
* The s-expression must be "KiCad compatible". See Documentation/s-expressions.txt
* for this KiCad compatible definition (it is the non-specctra mode).
* And also see in tools/property_tree.cpp for example usage.
*
* <code>
*
* FILE* fp = fopen( argv[1], "r" );
*
* static const KEYWORD empty_keywords[1] = {};
*
* DSNLEXER lexer( empty_keywords, 0, fp, wxString( FROM_UTF8( argv[1] ) ) );
*
* try
* {
* PTREE doc;
* Scan( &doc, &lexer );
* }
* catch( IO_ERROR ioe )
* {
* fprintf( stderr, "%s\n", TO_UTF8( ioe.errorText ) );
* }
*
* </code>
*/
void Scan( PTREE* aTree, DSNLEXER* aLexer ) throw ( IO_ERROR );
/**
* Function Format
* outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
*/
void Format( OUTPUTFORMATTER* out, int aNestLevel, int aCtl, CPTREE& aTree ) throw( IO_ERROR );
#endif // PTREE_H_

View File

@ -65,13 +65,14 @@ add_executable( test-nm-biu-to-ascii-mm-round-tripping
test-nm-biu-to-ascii-mm-round-tripping.cpp test-nm-biu-to-ascii-mm-round-tripping.cpp
) )
add_executable( parser_gen add_executable( property_tree
EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL
parser_gen.cpp property_tree.cpp
../common/richio.cpp ../common/richio.cpp
../common/dsnlexer.cpp ../common/dsnlexer.cpp
../common/ptree.cpp
) )
target_link_libraries( parser_gen target_link_libraries( property_tree
${wxWidgets_LIBRARIES} ${wxWidgets_LIBRARIES}
) )

View File

@ -1,290 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2012 KiCad Developers, see CHANGELOG.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
*/
// This is wanting to be an s-expression C++ parser generator. Feed it a sample
// file and maybe someday it will generate a C++ file which uses DSNLEXER to
// parse the described grammar OK.
// Until then, it is a non-specctra mode s-expression beautifier.
#include <assert.h>
#include <richio.h>
#include <dsnlexer.h>
#include <macros.h>
#include <boost/ptr_container/ptr_vector.hpp>
// http://sexpr.sourceforge.net/ see comments about graphviz
// http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx
#define D(x) x
//#define D(x)
/**
* Class ELEM
*/
class ELEM
{
protected:
int token;
std::string text;
typedef boost::ptr_vector<ELEM> ELEMS;
typedef ELEMS::const_iterator ELEMS_CITER;
typedef ELEMS::iterator ELEMS_ITER;
ELEMS kids; ///< ELEM pointers
public:
// there are two constructors, one for a list, one for an atom
/// List constructor
ELEM( int aToken ) :
token( aToken ),
text( "" )
{
// D( printf( "ELEM%p: list\n", this ); )
}
/// Atom constructor
ELEM( const std::string& aText, int aToken ) :
token( aToken ),
text( aText )
{
// D( printf( "ELEM%p: '%s'\n", this, text.c_str() ); )
}
int Token() const { return token; }
const char* Text() { return text.c_str(); }
/**
* Function Format
* writes this object as ASCII out to an OUTPUTFORMATTER
* @param out The formatter to write to.
* @param nestLevel A multiple of the number of spaces to preceed the output with.
* @throw IO_ERROR if a system error writing the output, such as a full disk.
*/
void Format( OUTPUTFORMATTER* aFormatter, int aNestLevel = 0, int aControlBits = 0 );
#define CTL_OMIT_NL (1<<0)
/**
* Function Length
* returns the number of ELEMs in this ELEM.
* @return int - the count of children
*/
int Length() const
{
return kids.size();
}
void Append( ELEM* aElem )
{
kids.push_back( aElem );
}
ELEM* Replace( int aIndex, ELEM* aElem )
{
ELEMS::auto_type ret = kids.replace( aIndex, aElem );
return ret.release();
}
ELEM* Remove( int aIndex )
{
ELEMS::auto_type ret = kids.release( kids.begin()+aIndex );
return ret.release();
}
void Insert( int aIndex, ELEM* aElem )
{
kids.insert( kids.begin()+aIndex, aElem );
}
ELEM* At( int aIndex ) const
{
const ELEM& ref = kids.at( aIndex );
return (ELEM*) &ref;
}
ELEM* operator[]( int aIndex ) const
{
return At( aIndex );
}
void Delete( int aIndex )
{
kids.erase( kids.begin()+aIndex );
}
};
void ELEM::Format( OUTPUTFORMATTER* out, int nestLevel, int ctl )
{
if( token == DSN_LEFT ) // this is a list
{
out->Print( nestLevel, "(" );
const int count = Length();
for( int i=0; i<count; ++i )
{
ELEM* cur = At( i );
ELEM* next = i < count-1 ? At( i+1 ) : NULL;
if( i > 0 )
out->Print( 0, " " );
if( next && next->token == DSN_LEFT )
{
cur->Format( out, nestLevel+1, 0 );
}
else
{
cur->Format( out, nestLevel+1, CTL_OMIT_NL );
}
}
out->Print( 0, ")%s", ctl & CTL_OMIT_NL ? "" : "\n" );
}
else // this is an atom
{
const char* s = out->Quotes( text ).c_str();
out->Print( 0, "%s%s", s, ctl & CTL_OMIT_NL ? "" : "\n" );
}
}
ELEM* Scan( DSNLEXER* lex );
ELEM* ScanList( DSNLEXER* lex );
ELEM* ScanAtom( DSNLEXER* lex );
void usage()
{
fprintf( stderr, "Usage: parser_gen <grammar_s-expression_file>\n" );
exit( 1 );
}
static KEYWORD empty_keywords[1] = {};
ELEM* Scan( DSNLEXER* lex )
{
ELEM* elem = NULL;
int tok = lex->CurTok();
// conditionally read first token.
if( tok == DSN_NONE )
tok = lex->NextTok();
if( tok == DSN_EOF )
{
lex->Unexpected( DSN_EOF );
}
if( tok == DSN_LEFT )
{
elem = ScanList( lex );
}
else
{
elem = ScanAtom( lex );
}
return elem;
}
/**
* Function ScanList
* reads and returns a sexpList from the input stream.
*/
ELEM* ScanList( DSNLEXER* lex )
{
int tok;
ELEM* list = NULL;
assert( lex->CurTok() == DSN_LEFT );
list = new ELEM( DSN_LEFT );
while( ( tok = lex->NextTok() ) != DSN_RIGHT )
{
if( tok == DSN_EOF )
lex->Unexpected( DSN_EOF );
ELEM* elem = Scan( lex );
list->Append( elem );
}
return list;
}
ELEM* ScanAtom( DSNLEXER* lex )
{
return new ELEM( lex->CurText(), lex->CurTok() );
}
int main( int argc, char** argv )
{
if( argc != 2 )
{
usage();
}
FILE* fp = fopen( argv[1], "rt" );
if( !fp )
{
fprintf( stderr, "Unable to open '%s'\n", argv[1] );
usage();
}
DSNLEXER lexer( empty_keywords, 0, fp, wxString( FROM_UTF8( argv[1] ) ) );
try
{
ELEM* elem = Scan( &lexer );
if( elem )
{
STRING_FORMATTER sf;
elem->Format( &sf, 0 );
printf( "%s", sf.GetString().c_str() );
}
}
catch( IO_ERROR ioe )
{
fprintf( stderr, "%s\n", TO_UTF8( ioe.errorText ) );
}
}

91
tools/property_tree.cpp Normal file
View File

@ -0,0 +1,91 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2012 KiCad Developers, see CHANGELOG.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
*/
// This is a propertytree test utility.
// It can convert XML to sexpressions or beautify s-expressions in non-specctra mode.
#include <assert.h>
#include <ptree.h>
#include <richio.h>
#include <dsnlexer.h>
#include <macros.h>
#include <boost/property_tree/xml_parser.hpp>
void usage()
{
fprintf( stderr, "Usage: parser_gen <grammar_s-expression_file>\n" );
exit( 1 );
}
int main( int argc, char** argv )
{
if( argc != 2 )
{
usage();
}
FILE* fp = fopen( argv[1], "r" );
if( !fp )
{
fprintf( stderr, "Unable to open '%s'\n", argv[1] );
usage();
}
static const KEYWORD empty_keywords[1] = {};
DSNLEXER lexer( empty_keywords, 0, fp, wxString( FROM_UTF8( argv[1] ) ) );
try
{
PTREE doc;
#if 0
using namespace boost::property_tree;
read_xml( argv[1], doc, xml_parser::trim_whitespace | xml_parser::no_comments );
#else
Scan( &doc, &lexer );
#endif
#if 1
STRING_FORMATTER sf;
Format( &sf, 0, 0, doc );
printf( "%s", sf.GetString().c_str() );
#else
// writing the unchanged ptree in file2.xml
boost::property_tree::xml_writer_settings<char> settings( ' ', 2 );
write_xml( "/tmp/output.xml", doc, std::locale(), settings );
#endif
}
catch( IO_ERROR ioe )
{
fprintf( stderr, "%s\n", TO_UTF8( ioe.errorText ) );
}
}