Skip to content
Snippets Groups Projects
Config.cpp 28.3 KiB
Newer Older
//======================================================================================================================
//
//  This file is part of waLBerla. waLBerla 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 3 of
//  the License, or (at your option) any later version.
//
//  waLBerla 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
//  You should have received a copy of the GNU General Public License along
//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file Config.cpp
//! \ingroup core
//! \author Klaus Iglberger
//! \brief Implementation of a parameter file parser.
//
//======================================================================================================================

#include "Config.h"
#include "core/mpi/MPIManager.h"

#include <fstream>
#include <iostream>
#include <stdexcept>

#include <boost/algorithm/string/trim.hpp>

namespace walberla {
namespace config {



//======================================================================================================================
//
//  CONSTRUCTORS
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\fn Config::Config()
// \brief Default constructor for the Config class.
 */
Config::Config()
     : stateFlag_(true) // Internal status flag
     , error_()         // Container for all error messages
     , block_()         // Global parameter block
{}
//**********************************************************************************************************************



//======================================================================================================================
//
//  DESTRUCTOR
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\fn Config::~Config()
// \brief Destructor for the Config class.
 */
Config::~Config()
//**********************************************************************************************************************




//======================================================================================================================
//
//  OPERATORS
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\fn Config& Config::operator=( const Config& c )
// \brief Copy assignment operator for the Config class.
//
// \param c The Config object to be copied.
 */
Config& Config::operator=( const Config& c )
{
   if( &c == this ) return *this;

   stateFlag_ = c.stateFlag_;
   error_.str() = c.error_.str();
   block_ = c.block_;
   valueReplacements_ = c.valueReplacements_;

   return *this;
}
//**********************************************************************************************************************




//======================================================================================================================
//
//  UTILITY FUNCTIONS
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\fn void Config::readParameterFile( const char* const filename )
// \brief Extraction of the parameter file \p filename.
//
// \param filename Name of the parameter file.
// \return void
 */
void Config::readParameterFile( const char* const filename )
{

   WALBERLA_ROOT_SECTION(){
      parseFromFile( filename, block_, 1 );
   }

   WALBERLA_MPI_SECTION() {
      std::string fileContent="";
      int size=0;
      WALBERLA_ROOT_SECTION() {
         fileContent=block_.getString();
         size=int(fileContent.size());
         //std::cout<<"Sending contents of parameter file to other processes... ("<<size<<" bytes)"<<std::endl;
         }
      MPI_Bcast(&size,1,MPITrait<int>::type(),0,MPI_COMM_WORLD);
      char* buffer = new char[uint_c(size)];
      WALBERLA_ROOT_SECTION() {
         fileContent.copy(buffer,uint_c(size));
      }
      MPI_Bcast(buffer,size,MPITrait<char>::type(),0,MPI_COMM_WORLD);
      WALBERLA_NON_ROOT_SECTION() {
         std::string content(buffer,uint_c(size));
         parseFromString(content,block_,1);
      }
      delete[] buffer;
   }
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn void Config::parseFromFile( const char* filename, Block& block, unsigned int level )
// \brief Parses the given file and extracts the parameters to the given block.
//
// \param filename Name of the parameter file.
// \param block Parameter block for the found parameters.
// \param level The current inclusion level.
 */
void Config::parseFromFile( const char* filename, Block& block, unsigned int level )
{
   std::stringstream input;
   LineVector lineNumbers;
   std::string line, key, value;
   std::string::size_type pos1, pos2;
   unsigned int lineCounter(0);
   bool comment(false);


   /////////////////////////////////////////////
   // Preprocessing the parameter file stream

   std::ifstream in( filename, std::ifstream::in );
   if( !in.is_open() )
   {
      error_ << "   Error opening parameter input file '" << filename << "' !\n";
      stateFlag_ = false;
      return;
   }

   line.reserve( 100 );
   key.reserve( 100 );
   value.reserve( 100 );

   while( std::getline( in, line ) )
   {
      ++lineCounter;

      // Filtering comments
      if( comment ) {
         if( ( pos1 = line.find( "*/", 0 ) ) != std::string::npos ) {
            line.erase( line.begin(), line.begin()+ numeric_cast< std::string::difference_type >( pos1+2 ) );
            comment = false;
         }
         else continue;
      }

      if( ( pos1 = line.find( "//", 0 ) ) != std::string::npos ) {
         line.erase( line.begin() + numeric_cast< std::string::difference_type >( pos1 ), line.end() );
      }
      if( ( pos1 = line.find( "/*", 0 ) ) != std::string::npos ) {
         if( ( pos2 = line.find( "*/", pos1+2 ) ) != std::string::npos ) {
            line.replace( line.begin() + numeric_cast< std::string::difference_type >( pos1 ),
                          line.begin() + numeric_cast< std::string::difference_type >( pos2+2 ), " " );
         }
         else {
            line.erase( line.begin() + numeric_cast< std::string::difference_type >( pos1 ), line.end() );
            comment = true;
         }
      }

      //Adding whitespaces
      for( pos1=0; ; ++pos1 )
      {
         if( pos1 >= line.size() ) break;

         if( line[pos1] == '{' ) {
            line.replace( pos1, 1, " { " );
            pos1 += 2;
         }
         else if( line[pos1] == '}' ) {
            line.replace( pos1, 1, " } " );
            pos1 += 2;
         }
         else if( line[pos1] == ';' ) {
            line.replace( pos1, 1, " ; " );
            pos1 += 2;
         }
      }

      //Adding the line to the input string
      lineNumbers.push_back( Pair( input.tellp(), lineCounter ) );
      input << line << "\n";
   }

   in.close();


   //////////////////////////
   // Parameter extraction

   while( input >> key )
   {
      unsigned int commandLine = getLineNumber( lineNumbers, input.tellg() );

      if( input >> std::ws && input.eof() ) break;

      //Extraction of a parameter block
      else if( input.peek() == '{' )
      {
         input.ignore( 1 );
         Block& newBlock = block.createBlock( key );
         extractBlock( filename, input, newBlock, lineNumbers,
                       getLineNumber( lineNumbers, input.tellg() ), level );
      }

      else if( boost::algorithm::iequals( key, "include" ) )
      {
         if( std::getline( input, value, ';' ) && !input.eof() )
         {
            input.ignore( 1 );

            if( level == maxInclusionLevel ) {
               error_ << "   " << filename << ", line " << commandLine
                      << ": Include limit reached! Include directives are limited to "
                      << maxInclusionLevel << " levels!\n"
                      << "     Inclusion of file '" << value << "' is skipped!\n";
               stateFlag_ = false;
            }
            else {
               removeTrailingWhiteSpaces( value );
               std::string file( (filesystem::path( getDirectory( filename ) ) / value).string() );
               parseFromFile( file.c_str(), block, level+1 );
            }
         }
      }

      //Extraction of a single parameter
      else if( std::getline( input, value, ';' ) && !input.eof() )
      {
         input.ignore( 1 );
         while( (value.find("$(") != value.npos) && (value.find(')') != value.npos) ) {
            size_t s = value.find("$("); size_t e = value.find(')');
            ValueReplacementMap::iterator mkey = valueReplacements_.find( value.substr( s+2, e-s+1-3 ) );
            if(mkey != valueReplacements_.end()) {
               value.replace( s,e-s+1, mkey->second );
            }
            else {
               if(e!=value.npos)
                  value.erase(e,1);
               if(s!=value.npos)
                  value.erase(s,2);
            }
         }
         if( !block.addParameter( key, value.substr( 0, value.size()-1 ) ) )
         {
            error_ << "   " << filename << ", line " << commandLine
                   << ": Duplicate parameter '" << key << "' in";
            if( block.getKey().empty() ) error_ << " global section!\n";
            else error_ << " block '" << block.getKey() << "'\n";
            stateFlag_ = false;
         }
      }
   }
}
//**********************************************************************************************************************

//**********************************************************************************************************************
/*!\fn void Config::parseFromString( const std::string str, Block& block, unsigned int level )
// \brief Parses the given file and extracts the parameters to the given block.
//
// \param str Config-generated input string.
// \param block Parameter block for the found parameters.
// \param level The current inclusion level.
//
// By internal convention, each block first contains all parameter, and then all sub-blocks
 */
void Config::parseFromString( const std::string & str, Block& block, unsigned int level )
{
   std::string input = str;

   while (!input.empty()) {
      std::string::size_type f=input.find_first_of(" {");
      if (input[f]==' ') {
         std::string::size_type posSemicolon = input.substr(f+1).find(';');
         block.addParameter(input.substr(0,f),input.substr(f+1,posSemicolon));
         input = input.substr(f+1+posSemicolon+1);
      } else {
         int lev=1;
         bool inParam=false;
         std::string::size_type curly2=std::string::npos;
         for (std::string::size_type pos = f+1; pos<input.size(); ++pos) {
            if (input[pos]==' ' && !inParam) inParam=true;
            if (input[pos]==';' && inParam) inParam=false;
            if (input[pos]=='{' && !inParam) ++lev;
            if (input[pos]=='}' && !inParam) --lev;
            if (lev==0) {
               curly2=pos;
               break;
            }
         }
         Block& newBlock = block.createBlock( input.substr(0,f) );
         parseFromString(input.substr(f+1,curly2-f-1),newBlock,level+1);
         input=input.substr(curly2+1);
      }
   }
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn void Config::extractBlock( const char* filename, std::stringstream& input, Block& block,
//                                const LineVector& lineNumbers, unsigned int lineNumber,
//                                unsigned int level )
// \brief Extracting a parameter block from the preprocessed input stream.
//
// \param filename Name of the parameter file
// \param input The preprocessed input stream.
// \param block The block whose parameters are extracted.
// \param lineNumbers The line numbers belonging to the preprocessed input stream.
// \param lineNumber Starting line of the block in the parameter file.
// \param level The current inclusion level.
 */
void Config::extractBlock( const char* filename, std::stringstream& input, Block& block,
                           const LineVector& lineNumbers, unsigned int lineNumber,
                           unsigned int level )
{
   std::string key, value;

   while( input >> key )
   {
      if(  boost::algorithm::iequals( key, "}" ) ) {
         return;
      }
      else if( input >> std::ws && input.eof() ) {
         break;
      }

      unsigned int commandLine = getLineNumber( lineNumbers, input.tellg() );

      //Extraction of a parameter block
      if( input.peek() == '{' )
      {
         input.ignore( 1 );
         Block& newBlock = block.createBlock( key );
         extractBlock( filename, input, newBlock, lineNumbers,
               getLineNumber( lineNumbers, input.tellg() ), level );
      }

      else if(  boost::algorithm::iequals( key, "include" ) )
      {
         if( std::getline( input, value, ';' ) && !input.eof() )
         {
            input.ignore( 1 );

            if( level == maxInclusionLevel ) {
               error_ << "   " << filename << ", line " << commandLine
                  << ": Include limit reached! Include directives are limited to "
                  << maxInclusionLevel << " levels!\n"
                  << "     Inclusion of file '" << value << "' is skipped!\n";
               stateFlag_ = false;
            }
            else {
               removeTrailingWhiteSpaces( value );
               std::string file( (filesystem::path( getDirectory( filename ) ) / value).string() );
               parseFromFile( file.c_str(), block, level+1 );
            }
         }
      }

      //Extraction of a single parameter
      else if( std::getline( input, value, ';' ) && !input.eof() )
      {
         input.ignore( 1 );
         while( (value.find("$(") != value.npos) && (value.find(')') != value.npos) ) {
            size_t s = value.find("$("); size_t e = value.find(')');
            ValueReplacementMap::iterator mkey = valueReplacements_.find( value.substr( s+2, e-s+1-3 ) );
            if(mkey != valueReplacements_.end()) {
               value.replace( s,e-s+1, mkey->second );
            }
            else {
               if(e!=value.npos)
                  value.erase(e,1);
               if(s!=value.npos)
                  value.erase(s,2);
            }
         }
         if( !block.addParameter( key, value.substr( 0, value.size()-1 ) ) ) {
            error_ << "   " << filename << ", line " << commandLine
               << ": Duplicate parameter '" << key << "' in block '" << block.getKey() << "'!\n";
            stateFlag_ = false;
         }
      }
   }

   error_ << "   Missing '}' for " << block.getKey()
      << " block starting in line " << lineNumber << "\n";
   stateFlag_ = false;
   return;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn unsigned int Config::getLineNumber( const LineVector& lineNumbers, sstreamPos pos ) const
// \brief Calculation of the line number of a command.
//
// \param lineNumbers ?
// \param pos Position in the preprocessed input stream.
 */
unsigned int Config::getLineNumber( const LineVector& lineNumbers, sstreamPos pos ) const
{
   LineVector::const_iterator it=lineNumbers.begin();
   for( ; it!=lineNumbers.end()-1; ++it ) {
      if( (it+1)->first > pos )
         return it->second;
   }
   return it->second;
}
//**********************************************************************************************************************




//======================================================================================================================
//
//  CLASS FILEREADER::BLOCK
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\fn Config::Block::Block( const std::string& key )
// \brief Standard constructor for the Block class.
//
// \param key The key of the block (default argument = "").
 */
Config::Block::Block( const std::string& key )
   :key_(key),blocks_(),params_()
{}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn Config::Block& Config::Block::operator=( const Block& b )
// \brief The copy assignment operator for the Block class.
//
// \param b The Block object to be copied.
// \return Reference to the Config object.
 */
Config::Block& Config::Block::operator=( const Block& b )
{
   if( &b == this ) return *this;

   key_ = b.key_;
   blocks_ = b.blocks_;
   params_ = b.params_;

   return *this;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn Config::Block::~Block()
// \brief Default destructor of the Block class.
 */
Config::Block::~Block()
//**********************************************************************************************************************



//**********************************************************************************************************************
/*!\fn Config::size_type Config::Block::getNumBlocks( const std::string& key ) const
// \brief Returns the number of contained parameter blocks corresponding to given key.
//
// \param key The key of the blocks to count.
//
// \return The number of contained parameter blocks corresponding to given key.
 */
Config::size_type Config::Block::getNumBlocks( const std::string& key ) const
{
   size_type c = 0;
   for( List::const_iterator it=blocks_.begin(); it!=blocks_.end(); ++it ) {
      if(  boost::algorithm::iequals( key, it->getKey() ) ) {
         ++c;
      }
   }
   return c;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn Config::BlockHandle Config::Block::getBlock( const std::string& key ) const
// \brief Returns one or none block that matches with \p key.
//
// \param key The key of the extracted block.
// \return One or none block that matches with \p key.
 */
Config::BlockHandle Config::Block::getBlock( const std::string& key ) const {

   Blocks blocks;
   getBlocks( key, blocks, 0, 1 );
   if( blocks.empty() )
      return BlockHandle();
   return blocks[0];
}

//**********************************************************************************************************************
/*!\fn Config::BlockHandle Config::Block::getOneBlock( const std::string& key ) const
// \brief Returns exactly one block that matches with \p key.
//
// \param key The key of the extracted block.
// \return One block that matches with \p key.
 */
Config::BlockHandle Config::Block::getOneBlock( const std::string& key ) const
{
   Blocks blocks;
   getBlocks( key, blocks, 1, 1 );
   return blocks[0];
}

//**********************************************************************************************************************
/*!\fn void Config::Block::getBlocks( const std::string& key, Blocks& blocks,
//                                    size_t min, size_t max ) const
// \brief Adds to the given \p blocks all extracted blocks with key \p key.
//
// \param key The key of the extracted blocks.
// \param[out] blocks Reference to the vector of blocks
// \param min minimum number of blocks the caller expects
// \param max maximum number of blocks the caller can handle
// \return void.
 */
void Config::Block::getBlocks( const std::string& key, Blocks& blocks, size_t min, size_t max ) const
{
   size_t c = 0;
   for( List::const_iterator it=blocks_.begin(); it!=blocks_.end(); ++it ) {
      if(  boost::algorithm::iequals( key, it->getKey() ) ) {
         blocks.push_back( BlockHandle( &*it ) );
         ++c;
      }
   }
   if( c < min || c > max ) {
      std::ostringstream oss;
      oss << "You requested at least " << min << " and at most " << max << " Config::Blocks with key "
          << key << " - but there are " << c << ".";
      throw std::range_error( oss.str() );
   }
}
//**********************************************************************************************************************

//**********************************************************************************************************************
/*!\fn void Config::Block::getBlocks( Blocks& blocks ) const
// \brief Adds to the given \p blocks all blocks contained in this block.
//
// \param[out] blocks Reference to the vector of blocks
// \return void.
 */
void Config::Block::getBlocks( Blocks& blocks ) const
{
   for( List::const_iterator it=blocks_.begin(); it!=blocks_.end(); ++it ) {
      blocks.push_back( BlockHandle( &*it ) );
   }
}
//**********************************************************************************************************************

//**********************************************************************************************************************
/*!\fn void Config::Block::getWritableBlocks( std::vector<Block*>& blocks )
// \brief Adds to the given \p blocks all blocks contained in this block.
//
// \param[out] blocks Reference to the vector of blocks
// \return void.
 */
void Config::Block::getWritableBlocks( std::vector<Block*> & blocks )
{
   for( List::iterator it=blocks_.begin(); it!=blocks_.end(); ++it ) {
      blocks.push_back( &*it );
   }
}
//**********************************************************************************************************************

//**********************************************************************************************************************
/*!\fn void Config::Block::getWritableBlocks( const std::string& key,std::vector<Block*>& blocks,size_t min,size_t max )
// \brief Adds to the given \p blocks all extracted blocks with key \p key.
//
// \param key The key of the extracted blocks.
// \param[out] blocks Reference to the vector of blocks
// \param min minimum number of blocks the caller expects
// \param max maximum number of blocks the caller can handle
// \return void.
 */
void Config::Block::getWritableBlocks( const std::string & key, std::vector<Block*> & blocks, size_t min, size_t max )
{
   size_t c = 0;
   for( List::iterator it=blocks_.begin(); it!=blocks_.end(); ++it ) {
      if(  boost::algorithm::iequals( key, it->getKey() ) ) {
         blocks.push_back(  &*it );
         ++c;
      }
   }
   if( c < min || c > max ) {
      std::ostringstream oss;
      oss << "You requested at least " << min << " and at most " << max << " Config::Blocks with key "
          << key << " - but there are " << c << ".";
      throw std::range_error( oss.str() );
   }
}
//**********************************************************************************************************************




//======================================================================================================================
//
//  GET FUNCTIONS
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\fn bool Config::Block::addParameter( std::string key, const std::string& value )
// \brief Adding a parameter to a parameter block.
//
// \param key The key of the new parameter.
// \param value The value of the new parameter.
// \return \a true if a new parameter was added, \a false if duplicate key is present.
 */
bool Config::Block::addParameter( std::string key, const std::string& value )
{
   return params_.insert( std::pair<Key,Value>(key,value) ).second;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn Config::Block& Config::Block::createBlock( std::string key )
// \brief Adding a new block within the parameter block.
//
// \param key The key of the new block.
// \return Reference to the new block.
 */
Config::Block& Config::Block::createBlock( std::string key )
{
   blocks_.push_back( Block( key ) );
   return *blocks_.rbegin();
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\fn void Config::Block::listParameters() const
// \brief Output function for the contained parameters.
//
// \return void
 */
void Config::Block::listParameters() const
{
   for( Map::const_iterator it=params_.begin(); it!=params_.end(); ++it ) {
      std::cout << " Key = '" << it->first << "' , Value = '" << it->second << "'\n";
   }
}
//**********************************************************************************************************************

//**********************************************************************************************************************
/*!\fn std::string Config::Block::getString() const
// \brief Returns Config-internal string to use for communication.
//
// \return void
 */
std::string Config::Block::getString() const
{
   std::string ret="";
   for( Map::const_iterator it=params_.begin(); it!=params_.end(); ++it ) {
      ret += it->first + " " + it->second + ";";
   }
   for( List::const_iterator it=blocks_.begin(); it!=blocks_.end(); ++it ) {
      ret += it->key_ + "{" + it->getString() + "}";
   }
   return ret;
}
//**********************************************************************************************************************





//======================================================================================================================
//
//  OUTPUT
//
//======================================================================================================================
static void printConfig( std::ostream & os, const Config::BlockHandle & block, int depth = 0  )
{
   std::vector< Config::BlockHandle > subBlocks;
   block.getBlocks( subBlocks );

   std::stringstream prefix;
   for( int i=0; i < depth; ++i )
      prefix << "   ";


   if ( depth >=0 )
   {
      std::string blockName = block.getKey();
      boost::algorithm::trim( blockName );
      os << prefix.str() << blockName << "\n";
      os << prefix.str() << "{\n";
   }

   for( auto subBlock = subBlocks.begin(); subBlock != subBlocks.end(); ++subBlock )
      printConfig( os, *subBlock, depth+1);

   for( auto paramIt = block.begin(); paramIt != block.end(); ++paramIt ) {
      std::string key = paramIt->first;
      std::string value = paramIt->second;
      boost::algorithm::trim( key );
      boost::algorithm::trim( value );
      os << prefix.str() << "   " << key << " " << value << ";\n";
   }


   if ( depth >=0 )
      os << prefix.str() << "}\n";
}


std::ostream & operator<< ( std::ostream & os, const Config & config )
{
   printConfig( os, config.getGlobalBlock(), -1 );
   return os;
}

std::ostream & operator<< ( std::ostream & os, const Config::BlockHandle & block )
{
   printConfig( os, block );
   return os;
}



} // namespace config
} // namespace walberla