Files
opm-common/opm/parser/eclipse/Parser/Parser.cpp

976 lines
36 KiB
C++

/*
Copyright 2013 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
OPM 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.
OPM 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 OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cctype>
#include <fstream>
#include <memory>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <opm/json/JsonObject.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/DeckItem.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Deck/DeckRecord.hpp>
#include <opm/parser/eclipse/Deck/Section.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ParserIntItem.hpp>
#include <opm/parser/eclipse/Parser/ParserKeyword.hpp>
#include <opm/parser/eclipse/Parser/ParserRecord.hpp>
#include <opm/parser/eclipse/RawDeck/RawConsts.hpp>
#include <opm/parser/eclipse/RawDeck/RawEnums.hpp>
#include <opm/parser/eclipse/RawDeck/RawKeyword.hpp>
#include <opm/parser/eclipse/Utility/Stringview.hpp>
namespace Opm {
namespace {
template< typename Itr >
inline Itr find_comment( Itr begin, Itr end ) {
auto itr = std::find( begin, end, '-' );
for( ; itr != end; itr = std::find( itr + 1, end, '-' ) )
if( (itr + 1) != end && *( itr + 1 ) == '-' ) return itr;
return end;
}
template< typename Itr, typename Term >
inline Itr find_terminator( Itr begin, Itr end, Term terminator ) {
auto pos = terminator( begin, end );
if( pos == end ) return end;
auto qbegin = std::find_if( begin, end, RawConsts::is_quote );
if( qbegin == end || qbegin > pos )
return pos;
auto qend = std::find( qbegin + 1, end, *qbegin );
// Quotes are not balanced - probably an error?!
if( qend == end ) return end;
return find_terminator( qend + 1, end, terminator );
}
/**
This function will return a copy of the input string where all
characters following '--' are removed. The copy is a view and relies on
the source string to remain alive. The function handles quoting with
single quotes and double quotes:
ABC --Comment => ABC
ABC '--Comment1' --Comment2 => ABC '--Comment1'
ABC "-- Not balanced quote? => ABC "-- Not balanced quote?
*/
static inline string_view strip_comments( string_view str ) {
return { str.begin(), find_terminator( str.begin(), str.end(),
find_comment< string_view::const_iterator > ) };
}
template< typename Itr >
inline Itr trim_left( Itr begin, Itr end ) {
return std::find_if_not( begin, end, RawConsts::is_separator );
}
template< typename Itr >
inline Itr trim_right( Itr begin, Itr end ) {
std::reverse_iterator< Itr > rbegin( end );
std::reverse_iterator< Itr > rend( begin );
return std::find_if_not( rbegin, rend, RawConsts::is_separator ).base();
}
inline string_view trim( string_view str ) {
auto fst = trim_left( str.begin(), str.end() );
auto lst = trim_right( fst, str.end() );
return { fst, lst };
}
inline string_view strip_slash( string_view view ) {
using itr = string_view::const_iterator;
const auto term = []( itr begin, itr end ) {
return std::find( begin, end, '/' );
};
auto begin = view.begin();
auto end = view.end();
auto slash = find_terminator( begin, end, term );
/* we want to preserve terminating slashes */
if( slash != end ) ++slash;
return { begin, slash };
}
inline bool getline( string_view& input, string_view& line ) {
if( input.empty() ) return false;
auto end = std::find( input.begin(), input.end(), '\n' );
line = string_view( input.begin(), end );
input = string_view( end + 1, input.end() );
return true;
/* we know that we always append a newline onto the input string, so we can
* safely assume that end+1 will either be end-of-input (i.e. empty range)
* or the start of the next line
*/
}
/*
* Read the input file and remove everything that isn't interesting data,
* including stripping comments, removing leading/trailing whitespaces and
* everything after (terminating) slashes
*/
inline std::string clean( const std::string& str ) {
std::string dst;
dst.reserve( str.size() );
string_view input( str ), line;
while( getline( input, line ) ) {
line = trim( strip_slash( strip_comments( line ) ) );
//if( line.begin() == line.end() ) continue;
dst.append( line.begin(), line.end() );
dst.push_back( '\n' );
}
struct f {
bool inside_quotes = false;
bool operator()( char c ) {
if( c == ',' ) return true;
if( RawConsts::is_quote( c ) ) inside_quotes = !inside_quotes;
return false;
}
};
/* some decks use commas for item separation in records, but commas add
* nothing over whitespace. run over the deck and replace all non-quoted
* commas with whitespace. commas withing quotes are read verbatim and not
* to be touched.
*
* There's a known defect: considering the record
* foo bar, , , baz, , /
* baz will silently interpreted as item #3 instead of item #5. As of
* writing we're not sure if this is even legal, nor have we seen it
* happen, but the effort needed to support it is tremendous and will
* require significant changes throughout.
*/
std::replace_if( dst.begin(), dst.end(), f(), ' ' );
return dst;
}
const std::string emptystr = "";
struct file {
file( boost::filesystem::path p, const std::string& in ) :
input( in ), path( p )
{}
string_view input;
size_t lineNR = 0;
boost::filesystem::path path;
};
class InputStack : public std::stack< file, std::vector< file > > {
public:
void push( std::string&& input, boost::filesystem::path p = "" );
private:
std::list< std::string > string_storage;
using base = std::stack< file, std::vector< file > >;
};
void InputStack::push( std::string&& input, boost::filesystem::path p ) {
this->string_storage.push_back( std::move( input ) );
this->emplace( p, this->string_storage.back() );
}
class ParserState {
public:
ParserState( const ParseContext& );
ParserState( const ParseContext&, boost::filesystem::path );
void loadString( const std::string& );
void loadFile( const boost::filesystem::path& );
void openRootFile( const boost::filesystem::path& );
void handleRandomText(const string_view& ) const;
boost::filesystem::path getIncludeFilePath( std::string ) const;
void addPathAlias( const std::string& alias, const std::string& path );
const boost::filesystem::path& current_path() const;
size_t line() const;
bool done() const;
string_view getline();
void closeFile();
private:
InputStack input_stack;
std::map< std::string, std::string > pathMap;
boost::filesystem::path rootPath;
public:
RawKeywordPtr rawKeyword;
string_view nextKeyword = emptystr;
Deck* deck;
const ParseContext& parseContext;
};
const boost::filesystem::path& ParserState::current_path() const {
return this->input_stack.top().path;
}
size_t ParserState::line() const {
return this->input_stack.top().lineNR;
}
bool ParserState::done() const {
while( !this->input_stack.empty() &&
this->input_stack.top().input.empty() )
const_cast< ParserState* >( this )->input_stack.pop();
return this->input_stack.empty();
}
string_view ParserState::getline() {
string_view ln;
Opm::getline( this->input_stack.top().input, ln );
this->input_stack.top().lineNR++;
return ln;
}
void ParserState::closeFile() {
this->input_stack.pop();
}
ParserState::ParserState(const ParseContext& __parseContext)
: deck( new Deck() ),
parseContext( __parseContext )
{}
ParserState::ParserState( const ParseContext& context,
boost::filesystem::path p ) :
rootPath( boost::filesystem::canonical( p ).parent_path() ),
deck( new Deck() ),
parseContext( context )
{
openRootFile( p );
}
void ParserState::loadString(const std::string& input) {
this->input_stack.push( clean( input + "\n" ) );
}
void ParserState::loadFile(const boost::filesystem::path& inputFile) {
boost::filesystem::path inputFileCanonical;
try {
inputFileCanonical = boost::filesystem::canonical(inputFile);
} catch (boost::filesystem::filesystem_error fs_error) {
std::string msg = "Could not open file: " + inputFile.string();
parseContext.handleError( ParseContext::PARSE_MISSING_INCLUDE , deck->getMessageContainer() , msg);
return;
}
const auto closer = []( std::FILE* f ) { std::fclose( f ); };
std::unique_ptr< std::FILE, decltype( closer ) > ufp(
std::fopen( inputFileCanonical.string().c_str(), "rb" ),
closer
);
// make sure the file we'd like to parse is readable
if( !ufp ) {
std::string msg = "Could not read from file: " + inputFile.string();
parseContext.handleError( ParseContext::PARSE_MISSING_INCLUDE , deck->getMessageContainer() , msg);
return;
}
/*
* read the input file C-style. This is done for performance
* reasons, as streams are slow
*/
auto* fp = ufp.get();
std::string buffer;
std::fseek( fp, 0, SEEK_END );
buffer.resize( std::ftell( fp ) + 1 );
std::rewind( fp );
const auto readc = std::fread( &buffer[ 0 ], 1, buffer.size() - 1, fp );
buffer.back() = '\n';
if( std::ferror( fp ) || readc != buffer.size() - 1 )
throw std::runtime_error( "Error when reading input file '"
+ inputFileCanonical.string() + "'" );
this->input_stack.push( clean( buffer ), inputFileCanonical );
}
/*
* We have encountered 'random' characters in the input file which
* are not correctly formatted as a keyword heading, and not part
* of the data section of any keyword.
*/
void ParserState::handleRandomText(const string_view& keywordString ) const {
std::string errorKey;
std::stringstream msg;
std::string trimmedCopy = keywordString.string();
if (trimmedCopy == "/") {
errorKey = ParseContext::PARSE_RANDOM_SLASH;
msg << "Extra '/' detected at: "
<< this->current_path()
<< ":" << this->line();
} else {
errorKey = ParseContext::PARSE_RANDOM_TEXT;
msg << "String \'" << keywordString
<< "\' not formatted/recognized as valid keyword at: "
<< this->current_path()
<< ":" << this->line();
}
parseContext.handleError( errorKey , deck->getMessageContainer() , msg.str() );
}
void ParserState::openRootFile( const boost::filesystem::path& inputFile) {
this->loadFile( inputFile );
this->deck->setDataFile( inputFile.string() );
const boost::filesystem::path& inputFileCanonical = boost::filesystem::canonical(inputFile);
rootPath = inputFileCanonical.parent_path();
}
boost::filesystem::path ParserState::getIncludeFilePath( std::string path ) const {
static const std::string pathKeywordPrefix("$");
static const std::string validPathNameCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
size_t positionOfPathName = path.find(pathKeywordPrefix);
if ( positionOfPathName != std::string::npos) {
std::string stringStartingAtPathName = path.substr(positionOfPathName+1);
size_t cutOffPosition = stringStartingAtPathName.find_first_not_of(validPathNameCharacters);
std::string stringToFind = stringStartingAtPathName.substr(0, cutOffPosition);
std::string stringToReplace = this->pathMap.at( stringToFind );
boost::replace_all(path, pathKeywordPrefix + stringToFind, stringToReplace);
}
boost::filesystem::path includeFilePath(path);
if (includeFilePath.is_relative())
return this->rootPath / includeFilePath;
return includeFilePath;
}
void ParserState::addPathAlias( const std::string& alias, const std::string& path ) {
this->pathMap.emplace( alias, path );
}
std::shared_ptr< RawKeyword > createRawKeyword( const string_view& kw, ParserState& parserState, const Parser& parser ) {
auto keywordString = ParserKeyword::getDeckName( kw );
if( !parser.isRecognizedKeyword( keywordString ) ) {
if( ParserKeyword::validDeckName( keywordString ) ) {
std::string msg = "Keyword " + keywordString + " not recognized.";
auto& msgContainer = parserState.deck->getMessageContainer();
parserState.parseContext.handleError( ParseContext::PARSE_UNKNOWN_KEYWORD, msgContainer, msg );
return {};
}
parserState.handleRandomText( keywordString );
return {};
}
const auto* parserKeyword = parser.getParserKeywordFromDeckName( keywordString );
if( parserKeyword->getSizeType() == SLASH_TERMINATED || parserKeyword->getSizeType() == UNKNOWN) {
const auto rawSizeType = parserKeyword->getSizeType() == SLASH_TERMINATED
? Raw::SLASH_TERMINATED
: Raw::UNKNOWN;
return std::make_shared< RawKeyword >( keywordString, rawSizeType,
parserState.current_path().string(),
parserState.line() );
}
if( parserKeyword->hasFixedSize() ) {
return std::make_shared< RawKeyword >( keywordString,
parserState.current_path().string(),
parserState.line(),
parserKeyword->getFixedSize(),
parserKeyword->isTableCollection() );
}
const auto& sizeKeyword = parserKeyword->getSizeDefinitionPair();
const auto* deck = parserState.deck;
if( deck->hasKeyword(sizeKeyword.first ) ) {
const auto& sizeDefinitionKeyword = deck->getKeyword(sizeKeyword.first);
const auto& record = sizeDefinitionKeyword.getRecord(0);
const auto targetSize = record.getItem( sizeKeyword.second ).get< int >( 0 );
return std::make_shared< RawKeyword >( keywordString,
parserState.current_path().string(),
parserState.line(),
targetSize,
parserKeyword->isTableCollection() );
}
std::string msg = "Expected the kewyord: " + sizeKeyword.first
+ " to infer the number of records in: " + keywordString;
auto& msgContainer = parserState.deck->getMessageContainer();
parserState.parseContext.handleError(ParseContext::PARSE_MISSING_DIMS_KEYWORD , msgContainer, msg );
const auto* keyword = parser.getKeyword( sizeKeyword.first );
const auto record = keyword->getRecord(0);
const auto int_item = std::dynamic_pointer_cast<const ParserIntItem>( record->get( sizeKeyword.second ) );
const auto targetSize = int_item->getDefault( );
return std::make_shared< RawKeyword >( keywordString,
parserState.current_path().string(),
parserState.line(),
targetSize,
parserKeyword->isTableCollection() );
}
bool tryParseKeyword( ParserState& parserState, const Parser& parser ) {
if (parserState.nextKeyword.length() > 0) {
parserState.rawKeyword = createRawKeyword( parserState.nextKeyword, parserState, parser );
parserState.nextKeyword = emptystr;
}
if (parserState.rawKeyword && parserState.rawKeyword->isFinished())
return true;
while( !parserState.done() ) {
auto line = parserState.getline();
if( line.empty() && !parserState.rawKeyword ) continue;
if( line.empty() && !parserState.rawKeyword->is_title() ) continue;
std::string keywordString;
if( parserState.rawKeyword == NULL ) {
if( RawKeyword::isKeywordPrefix( line, keywordString ) ) {
parserState.rawKeyword = createRawKeyword( keywordString, parserState, parser );
} else
/* We are looking at some random gibberish?! */
parserState.handleRandomText( line );
} else {
if (parserState.rawKeyword->getSizeType() == Raw::UNKNOWN) {
if( parser.isRecognizedKeyword( line ) ) {
parserState.rawKeyword->finalizeUnknownSize();
parserState.nextKeyword = line;
return true;
}
}
parserState.rawKeyword->addRawRecordString(line);
}
if (parserState.rawKeyword
&& parserState.rawKeyword->isFinished()
&& parserState.rawKeyword->getSizeType() != Raw::UNKNOWN)
{
return true;
}
}
if (parserState.rawKeyword
&& parserState.rawKeyword->getSizeType() == Raw::UNKNOWN)
{
parserState.rawKeyword->finalizeUnknownSize();
}
return false;
}
bool parseState( ParserState& parserState, const Parser& parser ) {
while( !parserState.done() ) {
parserState.rawKeyword.reset();
const bool streamOK = tryParseKeyword( parserState, parser );
if( !parserState.rawKeyword && !streamOK )
continue;
if (parserState.rawKeyword->getKeywordName() == Opm::RawConsts::end)
return true;
if (parserState.rawKeyword->getKeywordName() == Opm::RawConsts::endinclude) {
parserState.closeFile();
continue;
}
if (parserState.rawKeyword->getKeywordName() == Opm::RawConsts::paths) {
for( const auto& record : *parserState.rawKeyword ) {
std::string pathName = readValueToken<std::string>(record.getItem(0));
std::string pathValue = readValueToken<std::string>(record.getItem(1));
parserState.addPathAlias( pathName, pathValue );
}
continue;
}
if (parserState.rawKeyword->getKeywordName() == Opm::RawConsts::include) {
auto& firstRecord = parserState.rawKeyword->getFirstRecord( );
std::string includeFileAsString = readValueToken<std::string>(firstRecord.getItem(0));
boost::filesystem::path includeFile = parserState.getIncludeFilePath( includeFileAsString );
parserState.loadFile( includeFile );
continue;
}
if( parser.isRecognizedKeyword( parserState.rawKeyword->getKeywordName() ) ) {
const auto& kwname = parserState.rawKeyword->getKeywordName();
const auto* parserKeyword = parser.getParserKeywordFromDeckName( kwname );
parserState.deck->addKeyword( parserKeyword->parse( parserState.parseContext, parserState.deck->getMessageContainer(), parserState.rawKeyword ) );
} else {
DeckKeyword deckKeyword( parserState.rawKeyword->getKeywordName(), false );
const std::string msg = "The keyword " + parserState.rawKeyword->getKeywordName() + " is not recognized";
deckKeyword.setLocation( parserState.rawKeyword->getFilename(),
parserState.rawKeyword->getLineNR());
parserState.deck->addKeyword( std::move( deckKeyword ) );
parserState.deck->getMessageContainer().warning(
parserState.current_path().string(), msg, parserState.line() );
}
}
return true;
}
}
/* stripComments only exists so that the unit tests can verify it.
* strip_comment is the actual (internal) implementation
*/
std::string Parser::stripComments( const std::string& str ) {
return { str.begin(), find_terminator( str.begin(), str.end(),
find_comment< std::string::const_iterator > ) };
}
Parser::Parser(bool addDefault) {
if (addDefault)
addDefaultKeywords();
}
/*
About INCLUDE: Observe that the ECLIPSE parser is slightly unlogical
when it comes to nested includes; the path to an included file is always
interpreted relative to the filesystem location of the DATA file, and
not the location of the file issuing the INCLUDE command. That behaviour
is retained in the current implementation.
*/
Deck * Parser::newDeckFromFile(const std::string &dataFileName, const ParseContext& parseContext) const {
ParserState parserState( parseContext, dataFileName );
parseState( parserState, *this );
applyUnitsToDeck( *parserState.deck );
return parserState.deck;
}
Deck * Parser::newDeckFromString(const std::string &data, const ParseContext& parseContext) const {
ParserState parserState( parseContext );
parserState.loadString( data );
parseState( parserState, *this );
applyUnitsToDeck( *parserState.deck );
return parserState.deck;
}
inline void assertFullDeck(const ParseContext& context) {
if (context.hasKey(ParseContext::PARSE_MISSING_SECTIONS))
throw new std::logic_error("Cannot construct a state in partial deck context");
}
EclipseState Parser::parse(const std::string &filename, const ParseContext& context) {
assertFullDeck(context);
Parser p;
DeckPtr deck = p.parseFile(filename, context);
EclipseState es(deck, context);
return es;
}
EclipseState Parser::parse(const Deck& deck, const ParseContext& context) {
assertFullDeck(context);
return EclipseState(deck, context);
}
EclipseState Parser::parseData(const std::string &data, const ParseContext& context) {
assertFullDeck(context);
Parser p;
auto deck = p.parseString(data, context);
return parse(*deck, context);
}
std::shared_ptr<const EclipseGrid> Parser::parseGrid(const std::string &filename, const ParseContext& context) {
if (context.hasKey(ParseContext::PARSE_MISSING_SECTIONS))
return std::make_shared<const EclipseGrid>(filename);
return parse(filename, context).getInputGrid();
}
std::shared_ptr<const EclipseGrid> Parser::parseGrid(const Deck& deck, const ParseContext& context)
{
if (context.hasKey(ParseContext::PARSE_MISSING_SECTIONS))
return std::make_shared<const EclipseGrid>(deck);
return parse(deck, context).getInputGrid();
}
std::shared_ptr<const EclipseGrid> Parser::parseGridData(const std::string &data, const ParseContext& context) {
Parser parser;
auto deck = parser.parseString(data, context);
if (context.hasKey(ParseContext::PARSE_MISSING_SECTIONS)) {
return std::make_shared<const EclipseGrid>(deck);
}
return parse(*deck, context).getInputGrid();
}
DeckPtr Parser::parseFile(const std::string &dataFileName, const ParseContext& parseContext) const {
return std::shared_ptr<Deck>( newDeckFromFile( dataFileName , parseContext));
}
DeckPtr Parser::parseString(const std::string &data, const ParseContext& parseContext) const {
return std::shared_ptr<Deck>( newDeckFromString( data , parseContext));
}
size_t Parser::size() const {
return m_deckParserKeywords.size();
}
const ParserKeyword* Parser::matchingKeyword(const string_view& name) const {
for (auto iter = m_wildCardKeywords.begin(); iter != m_wildCardKeywords.end(); ++iter) {
if (iter->second->matches(name))
return iter->second;
}
return nullptr;
}
bool Parser::hasWildCardKeyword(const std::string& internalKeywordName) const {
return (m_wildCardKeywords.count(internalKeywordName) > 0);
}
bool Parser::isRecognizedKeyword(const string_view& name ) const {
if( !ParserKeyword::validDeckName( name ) )
return false;
if( m_deckParserKeywords.count( name ) )
return true;
return matchingKeyword( name );
}
void Parser::addParserKeyword( std::unique_ptr< const ParserKeyword >&& parserKeyword) {
string_view name( parserKeyword->getName() );
auto* ptr = parserKeyword.get();
/* Store the keywords in the keyword storage. They aren't free'd until the
* parser gets destroyed, even if there is no reasonable way to reach them
* (effectively making them leak). This is not a big problem because:
*
* * A keyword can be added that overwrites some *but not all* deckname ->
* keyword mappings. Keeping track of this is more hassle than worth for
* what is essentially edge case usage.
* * We can store (and search) via string_view's from the keyword added
* first because we know that it will be kept around, i.e. we don't have to
* deal with subtle lifetime issues.
* * It means we aren't reliant on some internal name mapping, and can only
* be concerned with interesting behaviour.
* * Finally, these releases would in practice never happen anyway until
* the parser went out of scope, and now they'll also be cleaned up in the
* same sweep.
*/
this->keyword_storage.push_back( std::move( parserKeyword ) );
for (auto nameIt = ptr->deckNamesBegin();
nameIt != ptr->deckNamesEnd();
++nameIt)
{
m_deckParserKeywords[ *nameIt ] = ptr;
}
if (ptr->hasMatchRegex()) {
m_wildCardKeywords[ name ] = ptr;
}
}
void Parser::addParserKeyword(const Json::JsonObject& jsonKeyword) {
addParserKeyword( std::unique_ptr< ParserKeyword >( new ParserKeyword( jsonKeyword ) ) );
}
bool Parser::hasKeyword( const std::string& name ) const {
return this->m_deckParserKeywords.find( string_view( name ) )
!= this->m_deckParserKeywords.end();
}
const ParserKeyword* Parser::getKeyword( const std::string& name ) const {
return getParserKeywordFromDeckName( string_view( name ) );
}
const ParserKeyword* Parser::getParserKeywordFromDeckName(const string_view& name ) const {
auto candidate = m_deckParserKeywords.find( name );
if( candidate != m_deckParserKeywords.end() ) return candidate->second;
const auto* wildCardKeyword = matchingKeyword( name );
if ( !wildCardKeyword )
throw std::invalid_argument( "Do not have parser keyword for parsing: " + name );
return wildCardKeyword;
}
std::vector<std::string> Parser::getAllDeckNames () const {
std::vector<std::string> keywords;
for (auto iterator = m_deckParserKeywords.begin(); iterator != m_deckParserKeywords.end(); iterator++) {
keywords.push_back(iterator->first.string());
}
for (auto iterator = m_wildCardKeywords.begin(); iterator != m_wildCardKeywords.end(); iterator++) {
keywords.push_back(iterator->first.string());
}
return keywords;
}
void Parser::loadKeywords(const Json::JsonObject& jsonKeywords) {
if (jsonKeywords.is_array()) {
for (size_t index = 0; index < jsonKeywords.size(); index++) {
Json::JsonObject jsonKeyword = jsonKeywords.get_array_item(index);
addParserKeyword( std::unique_ptr< ParserKeyword >( new ParserKeyword( jsonKeyword ) ) );
}
} else
throw std::invalid_argument("Input JSON object is not an array");
}
bool Parser::loadKeywordFromFile(const boost::filesystem::path& configFile) {
try {
Json::JsonObject jsonKeyword(configFile);
ParserKeywordConstPtr parserKeyword = std::make_shared<const ParserKeyword>(jsonKeyword);
addParserKeyword( std::unique_ptr< ParserKeyword >( new ParserKeyword( jsonKeyword ) ) );
return true;
}
catch (...) {
return false;
}
}
void Parser::loadKeywordsFromDirectory(const boost::filesystem::path& directory, bool recursive) {
if (!boost::filesystem::exists(directory))
throw std::invalid_argument("Directory: " + directory.string() + " does not exist.");
else {
boost::filesystem::directory_iterator end;
for (boost::filesystem::directory_iterator iter(directory); iter != end; iter++) {
if (boost::filesystem::is_directory(*iter)) {
if (recursive)
loadKeywordsFromDirectory(*iter, recursive);
} else {
if (ParserKeyword::validInternalName(iter->path().filename().string())) {
if (!loadKeywordFromFile(*iter))
std::cerr << "** Warning: failed to load keyword from file:" << iter->path() << std::endl;
}
}
}
}
}
void Parser::applyUnitsToDeck(Deck& deck) const {
for( auto& deckKeyword : deck ) {
if( !isRecognizedKeyword( deckKeyword.name() ) ) continue;
const auto* parserKeyword = getParserKeywordFromDeckName( deckKeyword.name() );
if( !parserKeyword->hasDimension() ) continue;
parserKeyword->applyUnitsToDeck(deck , deckKeyword);
}
}
static bool isSectionDelimiter( const DeckKeyword& keyword ) {
const auto& name = keyword.name();
for( const auto& x : { "RUNSPEC", "GRID", "EDIT", "PROPS",
"REGIONS", "SOLUTION", "SUMMARY", "SCHEDULE" } )
if( name == x ) return true;
return false;
}
bool Section::checkSectionTopology(const Deck& deck,
const Parser& parser,
bool ensureKeywordSectionAffiliation)
{
if( deck.size() == 0 ) {
std::string msg = "empty decks are invalid\n";
deck.getMessageContainer().warning(msg);
return false;
}
bool deckValid = true;
if( deck.getKeyword(0).name() != "RUNSPEC" ) {
std::string msg = "The first keyword of a valid deck must be RUNSPEC\n";
auto curKeyword = deck.getKeyword(0);
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
std::string curSectionName = deck.getKeyword(0).name();
size_t curKwIdx = 1;
for (; curKwIdx < deck.size(); ++curKwIdx) {
const auto& curKeyword = deck.getKeyword(curKwIdx);
const std::string& curKeywordName = curKeyword.name();
if (!isSectionDelimiter( curKeyword )) {
if( !parser.isRecognizedKeyword( curKeywordName ) )
// ignore unknown keywords for now (i.e. they can appear in any section)
continue;
const auto& parserKeyword = parser.getParserKeywordFromDeckName( curKeywordName );
if (ensureKeywordSectionAffiliation && !parserKeyword->isValidSection(curSectionName)) {
std::string msg =
"The keyword '"+curKeywordName+"' is located in the '"+curSectionName
+"' section where it is invalid";
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
continue;
}
if (curSectionName == "RUNSPEC") {
if (curKeywordName != "GRID") {
std::string msg =
"The RUNSPEC section must be followed by GRID instead of "+curKeywordName;
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
curSectionName = curKeywordName;
}
else if (curSectionName == "GRID") {
if (curKeywordName != "EDIT" && curKeywordName != "PROPS") {
std::string msg =
"The GRID section must be followed by EDIT or PROPS instead of "+curKeywordName;
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
curSectionName = curKeywordName;
}
else if (curSectionName == "EDIT") {
if (curKeywordName != "PROPS") {
std::string msg =
"The EDIT section must be followed by PROPS instead of "+curKeywordName;
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
curSectionName = curKeywordName;
}
else if (curSectionName == "PROPS") {
if (curKeywordName != "REGIONS" && curKeywordName != "SOLUTION") {
std::string msg =
"The PROPS section must be followed by REGIONS or SOLUTION instead of "+curKeywordName;
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
curSectionName = curKeywordName;
}
else if (curSectionName == "REGIONS") {
if (curKeywordName != "SOLUTION") {
std::string msg =
"The REGIONS section must be followed by SOLUTION instead of "+curKeywordName;
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
curSectionName = curKeywordName;
}
else if (curSectionName == "SOLUTION") {
if (curKeywordName != "SUMMARY" && curKeywordName != "SCHEDULE") {
std::string msg =
"The SOLUTION section must be followed by SUMMARY or SCHEDULE instead of "+curKeywordName;
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
curSectionName = curKeywordName;
}
else if (curSectionName == "SUMMARY") {
if (curKeywordName != "SCHEDULE") {
std::string msg =
"The SUMMARY section must be followed by SCHEDULE instead of "+curKeywordName;
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
curSectionName = curKeywordName;
}
else if (curSectionName == "SCHEDULE") {
// schedule is the last section, so every section delimiter after it is wrong...
std::string msg =
"The SCHEDULE section must be the last one ("
+curKeywordName+" specified after SCHEDULE)";
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
}
// SCHEDULE is the last section and it is mandatory, so make sure it is there
if (curSectionName != "SCHEDULE") {
const auto& curKeyword = deck.getKeyword(deck.size() - 1);
std::string msg =
"The last section of a valid deck must be SCHEDULE (is "+curSectionName+")";
deck.getMessageContainer().warning(curKeyword.getFileName(), msg, curKeyword.getLineNumber());
deckValid = false;
}
return deckValid;
}
} // namespace Opm