diff --git a/opm/input/eclipse/Parser/ParseContext.hpp b/opm/input/eclipse/Parser/ParseContext.hpp index 6b890b8e7..186cc5284 100644 --- a/opm/input/eclipse/Parser/ParseContext.hpp +++ b/opm/input/eclipse/Parser/ParseContext.hpp @@ -314,6 +314,14 @@ class KeywordLocation; */ const static std::string ACTIONX_ILLEGAL_KEYWORD; + /* + Error flag marking parser errors ic ACTIONX conditions + */ + const static std::string ACTIONX_CONDITION_ERROR; + /* + Error flag marking that an ACTIONX has no condition + */ + const static std::string ACTIONX_NO_CONDITION; /* The RPTSCH, RPTSOL and RPTSCHED keywords have two alternative forms, diff --git a/opm/input/eclipse/Schedule/Action/ActionX.hpp b/opm/input/eclipse/Schedule/Action/ActionX.hpp index 62238a470..7f5a4a44e 100644 --- a/opm/input/eclipse/Schedule/Action/ActionX.hpp +++ b/opm/input/eclipse/Schedule/Action/ActionX.hpp @@ -75,7 +75,10 @@ class ActionX { public: ActionX(); ActionX(const std::string& name, size_t max_run, double max_wait, std::time_t start_time); - ActionX(const DeckKeyword& kw, const Actdims& actimds, std::time_t start_time); + ActionX(const std::string& name, size_t max_run, double max_wait, + std::time_t start_time, + const std::vector&& conditions, + const std::vector&& tokens); ActionX(const DeckRecord& record, std::time_t start_time); explicit ActionX(const RestartIO::RstAction& rst_action); @@ -131,6 +134,15 @@ private: std::vector m_conditions; }; +/// \brief Parse ActionX keyword. +/// \param kw The keyword representation of ActionX +/// \param actdims Dimensions for ActionX as specified in the deck. +/// \param start_time The first time that the ActionX should be evaluated +/// \return A tuple of the ActionX created and a vector that contains for each error experienced +/// during parsing the string indicating the type of error and the error string itself. +std::tuple>> +parseActionX(const DeckKeyword& kw, const Actdims& actimds, std::time_t start_time); + } } #endif /* WELL_HPP_ */ diff --git a/src/opm/input/eclipse/Parser/ParseContext.cpp b/src/opm/input/eclipse/Parser/ParseContext.cpp index 2cf4fb499..65f063eb2 100644 --- a/src/opm/input/eclipse/Parser/ParseContext.cpp +++ b/src/opm/input/eclipse/Parser/ParseContext.cpp @@ -114,6 +114,8 @@ namespace Opm { this->addKey(SUMMARY_REGION_TOO_LARGE, InputErrorAction::WARN); addKey(ACTIONX_ILLEGAL_KEYWORD, InputErrorAction::THROW_EXCEPTION); + addKey(ACTIONX_CONDITION_ERROR, InputErrorAction::THROW_EXCEPTION); + addKey(ACTIONX_NO_CONDITION, InputErrorAction::WARN); addKey(RPT_MIXED_STYLE, InputErrorAction::WARN); addKey(RPT_UNKNOWN_MNEMONIC, InputErrorAction::WARN); @@ -369,6 +371,8 @@ namespace Opm { const std::string ParseContext::SCHEDULE_INVALID_NAME = "SCHEDULE_INVALID_NAME"; const std::string ParseContext::ACTIONX_ILLEGAL_KEYWORD = "ACTIONX_ILLEGAL_KEYWORD"; + const std::string ParseContext::ACTIONX_CONDITION_ERROR = "ACTIONX_CONDITION_ERROR"; + const std::string ParseContext::ACTIONX_NO_CONDITION = "ACTIONX_NO_CONDITION"; const std::string ParseContext::SIMULATOR_KEYWORD_NOT_SUPPORTED = "SIMULATOR_KEYWORD_NOT_SUPPORTED"; const std::string ParseContext::SIMULATOR_KEYWORD_NOT_SUPPORTED_CRITICAL = "SIMULATOR_KEYWORD_NOT_SUPPORTED_CRITICAL"; diff --git a/src/opm/input/eclipse/Schedule/Action/ActionParser.cpp b/src/opm/input/eclipse/Schedule/Action/ActionParser.cpp index b7f0caed5..638091913 100644 --- a/src/opm/input/eclipse/Schedule/Action/ActionParser.cpp +++ b/src/opm/input/eclipse/Schedule/Action/ActionParser.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include #include "ActionParser.hpp" @@ -128,7 +130,9 @@ ParseNode Parser::current() const { Action::ASTNode Parser::parse_left() { auto current = this->current(); if (current.type != TokenType::ecl_expr) - return TokenType::error; + throw std::invalid_argument(fmt::format("Expected expression as left hand side " + "of comparison, but got {} instead.", + current.value)); std::string func = current.value; FuncType func_type = get_func(current.value); @@ -270,15 +274,17 @@ Action::ASTNode Parser::parse(const std::vector& tokens) { return ASTNode( start_node.type ); auto tree = parser.parse_or(); + + if (tree.type == TokenType::error) + throw std::invalid_argument("Failed to parse ACTIONX condition."); + auto current = parser.current(); if (current.type != TokenType::end) { size_t index = parser.current_pos; - throw std::invalid_argument("Extra unhandled data starting with token[" + std::to_string(index) + "] = " + current.value); + throw std::invalid_argument("Extra unhandled data starting with token[" + std::to_string(index) + "] = " + current.value+ + " in ACTIONX condition."); } - if (tree.type == TokenType::error) - throw std::invalid_argument("Failed to parse"); - return tree; } } diff --git a/src/opm/input/eclipse/Schedule/Action/ActionX.cpp b/src/opm/input/eclipse/Schedule/Action/ActionX.cpp index b9554a2e5..8216b97d0 100755 --- a/src/opm/input/eclipse/Schedule/Action/ActionX.cpp +++ b/src/opm/input/eclipse/Schedule/Action/ActionX.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,50 @@ bool ActionX::valid_keyword(const std::string& keyword) { return (actionx_allowed_list.find(keyword) != actionx_allowed_list.end()); } +std::tuple>> +parseActionX(const DeckKeyword& kw, const Actdims& actdims, + std::time_t start_time) +{ + std::vector> condition_errors; + std::vector tokens; + std::vector conditions; + auto record = kw.getRecord(0); + const std::string name = record.getItem("NAME").getTrimmedString(0); + + for (size_t record_index = 1; record_index < kw.size(); record_index++) { + const auto& cond_tokens = RawString::strings( kw.getRecord(record_index) + .getItem("CONDITION").getData() ); + + for (const auto& token : cond_tokens) + tokens.push_back(dequote(token, kw.location())); + + conditions.emplace_back(cond_tokens, kw.location()); + } + if (conditions.empty()) + condition_errors.push_back({ParseContext::ACTIONX_NO_CONDITION, + fmt::format("Action {} is missing a condition.", name)}); + + if (conditions.size() > actdims.max_conditions()) + condition_errors.push_back({ ParseContext::ACTIONX_CONDITION_ERROR, + fmt::format("Action {} has too many conditions - adjust item " + "4 of ACTDIMS to at least {}", + name, conditions.size())}); + + try + { + return { ActionX(name, + record.getItem("NUM").get(0), + record.getItem("MIN_WAIT").getSIDouble(0), + start_time, std::move(conditions), std::move(tokens)), + condition_errors}; + } + catch(const std::invalid_argument& e) + { + condition_errors.push_back({ ParseContext::ACTIONX_CONDITION_ERROR, + fmt::format("condition of action {} has the following error: {}", name, e.what())}); + return {ActionX(kw.getRecord(0), start_time), condition_errors}; + } +} ActionX::ActionX() : m_start_time(0) @@ -119,24 +164,13 @@ ActionX::ActionX(const DeckRecord& record, std::time_t start_time) : -ActionX::ActionX(const DeckKeyword& kw, const Actdims& actdims, std::time_t start_time) : - ActionX(kw.getRecord(0), start_time) -{ - std::vector tokens; - for (size_t record_index = 1; record_index < kw.size(); record_index++) { - const auto& record = kw.getRecord(record_index); - const auto& cond_tokens = RawString::strings( record.getItem("CONDITION").getData() ); - - for (const auto& token : cond_tokens) - tokens.push_back(dequote(token, kw.location())); - - this->m_conditions.emplace_back(cond_tokens, kw.location()); - } - if (this->m_conditions.size() > actdims.max_conditions()) - throw OpmInputError(fmt::format("Action {} has too many conditions - adjust item 4 of ACTDIMS to at least {}", this->name(), this->m_conditions.size()), kw.location()); - - this->condition = Action::AST(tokens); -} +ActionX::ActionX(const std::string& name, size_t max_run, double min_wait, + std::time_t start_time, + const std::vector&& conditions, + const std::vector&& tokens) + : m_name(name), m_max_run(max_run), m_min_wait(min_wait), + m_start_time(start_time), condition(tokens), m_conditions(conditions) +{} ActionX ActionX::serializationTestObject() diff --git a/src/opm/input/eclipse/Schedule/Schedule.cpp b/src/opm/input/eclipse/Schedule/Schedule.cpp index eee5219be..c684bce3e 100644 --- a/src/opm/input/eclipse/Schedule/Schedule.cpp +++ b/src/opm/input/eclipse/Schedule/Schedule.cpp @@ -665,9 +665,15 @@ void Schedule::iterateScheduleSection(std::size_t load_start, std::size_t load_e logger.location(location); if (keyword.is()) { - Action::ActionX action(keyword, - this->m_static.m_runspec.actdims(), - std::chrono::system_clock::to_time_t(this->snapshots[report_step].start_time())); + auto [action, condition_errors] = + Action::parseActionX(keyword, + this->m_static.m_runspec.actdims(), + std::chrono::system_clock::to_time_t(this->snapshots[report_step].start_time())); + + for(const auto& [ marker, msg]: condition_errors) { + parseContext.handleError(marker, msg, keyword.location(), errors); + } + while (true) { keyword_index++; if (keyword_index == block.size()) diff --git a/tests/parser/ACTIONX.cpp b/tests/parser/ACTIONX.cpp index b4c05129f..ee5eb3152 100644 --- a/tests/parser/ACTIONX.cpp +++ b/tests/parser/ACTIONX.cpp @@ -87,8 +87,27 @@ ACTIONX const auto deck = Parser{}.parseString( action_kw ); const auto& kw = deck["ACTIONX"].back(); - Action::ActionX action2(kw, {}, 0); + + const auto& [action2, condition_errors2 ] = + Action::parseActionX(kw, {}, 0); BOOST_CHECK_EQUAL(action2.name(), "ACTION"); + BOOST_CHECK_EQUAL(condition_errors2.size(), 0U); + + // left hand side has to be an expression. + // Check whether we add an error to condition_errors + // if that is not the case + const auto action_kw_num_first = std::string{ R"( +ACTIONX + 'ACTION' / + 0.75 < WWCT OPX / +/ +)"}; + + + const auto deck1 = Parser{}.parseString( action_kw_num_first); + const auto& [action3, condition_errors3] = + Action::parseActionX(deck1["ACTIONX"].back(), {}, 0); + BOOST_CHECK_EQUAL(condition_errors3.size(), 1U); }