From 212a1fde5f389d2bfbf45ce9b92bc8425e643a05 Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Wed, 7 Nov 2018 15:45:48 +0100 Subject: [PATCH] Parse condition part in ACTIONX --- CMakeLists_files.cmake | 2 + .../EclipseState/Schedule/ActionAST.hpp | 141 ++++++++ .../EclipseState/Schedule/ActionContext.hpp | 5 + .../eclipse/EclipseState/Schedule/ActionX.hpp | 2 + .../EclipseState/Schedule/ActionAST.cpp | 337 ++++++++++++++++++ .../EclipseState/Schedule/ActionContext.cpp | 13 + .../eclipse/EclipseState/Schedule/ActionX.cpp | 10 +- tests/parser/ACTIONX.cpp | 249 +++++++++++++ 8 files changed, 758 insertions(+), 1 deletion(-) create mode 100644 opm/parser/eclipse/EclipseState/Schedule/ActionAST.hpp create mode 100644 src/opm/parser/eclipse/EclipseState/Schedule/ActionAST.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index c8d100823..e650bb71c 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -74,6 +74,7 @@ if(ENABLE_ECL_INPUT) src/opm/parser/eclipse/EclipseState/IOConfig/IOConfig.cpp src/opm/parser/eclipse/EclipseState/IOConfig/RestartConfig.cpp src/opm/parser/eclipse/EclipseState/Runspec.cpp + src/opm/parser/eclipse/EclipseState/Schedule/ActionAST.cpp src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp src/opm/parser/eclipse/EclipseState/Schedule/Actions.cpp src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp @@ -440,6 +441,7 @@ if(ENABLE_ECL_INPUT) opm/parser/eclipse/EclipseState/Aquancon.hpp opm/parser/eclipse/EclipseState/AquiferCT.hpp opm/parser/eclipse/EclipseState/Aquifetp.hpp + opm/parser/eclipse/EclipseState/Schedule/ActionAST.hpp opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp opm/parser/eclipse/EclipseState/Schedule/Actions.hpp opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp diff --git a/opm/parser/eclipse/EclipseState/Schedule/ActionAST.hpp b/opm/parser/eclipse/EclipseState/Schedule/ActionAST.hpp new file mode 100644 index 000000000..642986f45 --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/ActionAST.hpp @@ -0,0 +1,141 @@ +/* + Copyright 2018 Equinor 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 . +*/ + + +#ifndef ActionAST_HPP +#define ActionAST_HPP + +#include +#include + + +namespace Opm { + +class ActionContext; + +enum TokenType { + number, // 0 + ecl_expr, // 1 + open_paren, // 2 + close_paren, // 3 + op_gt, // 4 + op_ge, // 5 + op_lt, // 6 + op_le, // 7 + op_eq, // 8 + op_ne, // 9 + op_and, // 10 + op_or, // 11 + end, // 12 + error // 13 +}; + +struct ParseNode { + ParseNode(TokenType type, const std::string& value) : + type(type), + value(value) + {} + + + ParseNode(TokenType type) : ParseNode(type, "") + {} + + + TokenType type; + std::string value; +}; + + +class ASTNode { +public: + +ASTNode() : + type(TokenType::error) +{} + + +ASTNode(TokenType type): + type(type) +{} + + +ASTNode(double value) : + type(TokenType::number), + number(value) +{} + + +ASTNode(TokenType type, const std::string& func, const std::vector& arg_list): + type(type), + func(func), + arg_list(arg_list) +{} + + bool eval(const ActionContext& context) const; + double value(const ActionContext& context) const; + TokenType type; + void add_child(const ASTNode& child); + size_t size() const; + +private: + std::string func; + std::vector arg_list; + double number; + + /* + To have a memmber std::vector inside the ASTNode class is + supposedly borderline undefined behaviour; it compiles without warnings + and works. Good for enough for me. + */ + std::vector children; +}; + + + +class ActionParser { +public: + ActionParser(const std::vector& tokens); + TokenType get_type(const std::string& arg) const; + ParseNode current() const; + ParseNode next(); + size_t pos() const; + void print() const; +private: + const std::vector& tokens; + ssize_t current_pos = -1; +}; + + +class ActionAST{ +public: + ActionAST() = default; + explicit ActionAST(const std::vector& tokens); + ASTNode parse_right(ActionParser& parser); + ASTNode parse_left(ActionParser& parser); + ASTNode parse_op(ActionParser& parser); + ASTNode parse_cmp(ActionParser& parser); + ASTNode parse_or(ActionParser& parser); + ASTNode parse_and(ActionParser& parser); + + bool eval(const ActionContext& context) const; +private: + ASTNode tree; +}; +} +#endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp b/opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp index bb507704a..d82fdf741 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp @@ -33,9 +33,14 @@ namespace Opm { class ActionContext { public: + ActionContext(); + double get(const std::string& func, const std::string& arg) const; void add(const std::string& func, const std::string& arg, double value); + double get(const std::string& func) const; + void add(const std::string& func, double value); + private: std::map values; }; diff --git a/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp b/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp index 1f3b62f2c..d1f8eea92 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp @@ -26,6 +26,7 @@ #include #include +#include namespace Opm { /* @@ -78,6 +79,7 @@ private: std::time_t m_start_time; std::vector keywords; + ActionAST ast; size_t run_count = 0; std::time_t last_run = 0; }; diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/ActionAST.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/ActionAST.cpp new file mode 100644 index 000000000..a9ec63bcb --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/ActionAST.cpp @@ -0,0 +1,337 @@ +/* + Copyright 2018 Equinor 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 . + */ + +#include +#include +#include +#include + +#include +#include + +namespace Opm { + +ActionParser::ActionParser(const std::vector& tokens) : + tokens(tokens) +{} + + +TokenType ActionParser::get_type(const std::string& arg) const { + std::string lower_arg = arg; + std::for_each(lower_arg.begin(), + lower_arg.end(), + [](char& c) { + c = std::tolower(static_cast(c)); + }); + + if (lower_arg == "and") + return TokenType::op_and; + + if (lower_arg == "or") + return TokenType::op_or; + + if (lower_arg == "(") + return TokenType::open_paren; + + if (lower_arg == ")") + return TokenType::close_paren; + + if (lower_arg == ">" || lower_arg == ".gt.") + return TokenType::op_gt; + + if (lower_arg == ">=" || lower_arg == ".ge.") + return TokenType::op_ge; + + if (lower_arg == "<=" || lower_arg == ".le.") + return TokenType::op_le; + + if (lower_arg == "<" || lower_arg == ".lt.") + return TokenType::op_lt; + + if (lower_arg == "<=" || lower_arg == ".le.") + return TokenType::op_le; + + if (lower_arg == "=" || lower_arg == ".eq.") + return TokenType::op_eq; + + if (lower_arg == "!=" || lower_arg == ".ne.") + return TokenType::op_ne; + + { + char * end_ptr; + strtod(lower_arg.c_str(), &end_ptr); + if (std::strlen(end_ptr) == 0) + return TokenType::number; + } + + return TokenType::ecl_expr; +} + +ParseNode ActionParser::next() { + this->current_pos++; + if (static_cast(this->current_pos) == this->tokens.size()) + return TokenType::end; + + std::string arg = this->tokens[this->current_pos]; + return ParseNode(get_type(arg), arg); +} + + +ParseNode ActionParser::current() const { + if (static_cast(this->current_pos) == this->tokens.size()) + return TokenType::end; + + std::string arg = this->tokens[this->current_pos]; + return ParseNode(get_type(arg), arg); +} + + +size_t ActionParser::pos() const { + return this->current_pos; +} + +/*****************************************************************/ + +void ASTNode::add_child(const ASTNode& child) { + this->children.push_back(child); +} + +double ASTNode::value(const ActionContext& context) const { + if (this->children.size() != 0) + throw std::invalid_argument("value() method should only reach leafnodes"); + + if (this->type == TokenType::number) + return this->number; + + if (this->arg_list.size() == 0) + return context.get(this->func); + else { + std::string arg_key = this->arg_list[0]; + for (size_t index = 1; index < this->arg_list.size(); index++) + arg_key += ":" + this->arg_list[index]; + return context.get(this->func, arg_key); + } +} + + +bool ASTNode::eval(const ActionContext& context) const { + if (this->children.size() == 0) + throw std::invalid_argument("bool eval should not reach leafnodes"); + + if (this->type == TokenType::op_or || this->type == TokenType::op_and) { + bool value = (this->type == TokenType::op_and); + for (const auto& child : this->children) { + if (this->type == TokenType::op_or) + value = value || child.eval(context); + else + value = value && child.eval(context); + } + return value; + } + + double v1 = this->children[0].value(context); + double v2 = this->children[1].value(context); + + switch (this->type) { + + case TokenType::op_eq: + return v1 == v2; + + case TokenType::op_ge: + return v1 >= v2; + + case TokenType::op_le: + return v1 <= v2; + + case TokenType::op_ne: + return v1 != v2; + + case TokenType::op_gt: + return v1 > v2; + + case TokenType::op_lt: + return v1 < v2; + + default: + throw std::invalid_argument("Incorrect operator type - expected comparison"); + } +} + + +size_t ASTNode::size() const { + return this->children.size(); +} + + +/*****************************************************************/ + +ASTNode ActionAST::parse_left(ActionParser& parser) { + auto current = parser.current(); + if (current.type != TokenType::ecl_expr) + return TokenType::error; + + std::string func = current.value; + std::vector arg_list; + current = parser.next(); + while (current.type == TokenType::ecl_expr || current.type == TokenType::number) { + arg_list.push_back(current.value); + current = parser.next(); + } + + return ASTNode(TokenType::ecl_expr, func, arg_list); +} + +ASTNode ActionAST::parse_op(ActionParser& parser) { + auto current = parser.current(); + if (current.type == TokenType::op_gt || + current.type == TokenType::op_ge || + current.type == TokenType::op_lt || + current.type == TokenType::op_le || + current.type == TokenType::op_eq || + current.type == TokenType::op_ne) { + parser.next(); + return current.type; + } + return TokenType::error; +} + + +ASTNode ActionAST::parse_right(ActionParser& parser) { + auto current = parser.current(); + if (current.type == TokenType::number) { + parser.next(); + return ASTNode( strtod(current.value.c_str(), nullptr) ); + } + + current = parser.current(); + if (current.type != TokenType::ecl_expr) + return TokenType::error; + + std::string func = current.value; + std::vector arg_list; + current = parser.next(); + while (current.type == TokenType::ecl_expr || current.type == TokenType::number) { + arg_list.push_back(current.value); + current = parser.next(); + } + return ASTNode(TokenType::ecl_expr, func, arg_list); +} + + + +ASTNode ActionAST::parse_cmp(ActionParser& parser) { + auto current = parser.current(); + + if (current.type == TokenType::open_paren) { + parser.next(); + auto inner_expr = this->parse_or(parser); + + current = parser.current(); + if (current.type != TokenType::close_paren) + return TokenType::error; + + parser.next(); + return inner_expr; + } else { + auto left_node = parse_left(parser); + if (left_node.type == TokenType::error) + return TokenType::error; + + auto op_node = parse_op(parser); + if (op_node.type == TokenType::error) + return TokenType::error; + + auto right_node = parse_right(parser); + if (right_node.type == TokenType::error) + return TokenType::error; + + op_node.add_child(left_node); + op_node.add_child(right_node); + return op_node; + } +} + +ASTNode ActionAST::parse_and(ActionParser& parser) { + auto left = this->parse_cmp(parser); + if (left.type == TokenType::error) + return TokenType::error; + + auto current = parser.current(); + if (current.type == TokenType::op_and) { + ASTNode and_node(TokenType::op_and); + and_node.add_child(left); + + while (parser.current().type == TokenType::op_and) { + parser.next(); + auto next_cmp = this->parse_cmp(parser); + if (next_cmp.type == TokenType::error) + return TokenType::error; + + and_node.add_child(next_cmp); + } + return and_node; + } + + return left; +} + + +ASTNode ActionAST::parse_or(ActionParser& parser) { + auto left = this->parse_and(parser); + if (left.type == TokenType::error) + return TokenType::error; + + auto current = parser.current(); + if (current.type == TokenType::op_or) { + ASTNode or_node(TokenType::op_or); + or_node.add_child(left); + + while (parser.current().type == TokenType::op_or) { + parser.next(); + auto next_cmp = this->parse_or(parser); + if (next_cmp.type == TokenType::error) + return TokenType::error; + + or_node.add_child(next_cmp); + } + return or_node; + } + + return left; +} + +ActionAST::ActionAST(const std::vector& tokens) { + ActionParser parser(tokens); + auto current = parser.next(); + this->tree = this->parse_or(parser); + current = parser.current(); + if (current.type != TokenType::end) { + size_t index = parser.pos(); + throw std::invalid_argument("Extra unhandled data starting with token[" + std::to_string(index) + "] = " + current.value); + } + + if (this->tree.type == TokenType::error) + throw std::invalid_argument("Failed to parse"); +} + +bool ActionAST::eval(const ActionContext& context) const { + return this->tree.eval(context); +} + +} diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp index d13bcd9e0..25a69b953 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp @@ -18,15 +18,28 @@ */ #include +#include namespace Opm { + ActionContext::ActionContext() { + for (const auto& pair : TimeMap::eclipseMonthIndices()) + this->add(pair.first, pair.second); + } + void ActionContext::add(const std::string& func, const std::string& arg, double value) { this->values[func + ":" + arg] = value; } + void ActionContext::add(const std::string& func, double value) { + this->values[func] = value; + } double ActionContext::get(const std::string& func, const std::string& arg) const { return this->values.at( func + ":" + arg ); } + + double ActionContext::get(const std::string& func) const { + return this->values.at( func ); + } } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp index 070455461..561a8d3b7 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp @@ -45,7 +45,15 @@ ActionX::ActionX(const DeckRecord& record, std::time_t start_time) : ActionX::ActionX(const DeckKeyword& kw, 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); + for (const auto& token : record.getItem("CONDITION").getData()) + tokens.push_back(token); + } + this->ast = ActionAST(tokens); +} void ActionX::addKeyword(const DeckKeyword& kw) { diff --git a/tests/parser/ACTIONX.cpp b/tests/parser/ACTIONX.cpp index 94560990f..44c06ff25 100644 --- a/tests/parser/ACTIONX.cpp +++ b/tests/parser/ACTIONX.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -198,3 +199,251 @@ BOOST_AUTO_TEST_CASE(TestContext) { context.add("FUNC", "ARG", 100); BOOST_CHECK_EQUAL(context.get("FUNC", "ARG"), 100); } + + + +Opm::Schedule make_action(const std::string& action_string) { + std::string start = std::string{ R"( +SCHEDULE +)"}; + std::string end = std::string{ R"( +ENDACTIO + +TSTEP + 10 / +)"}; + + std::string deck_string = start + action_string + end; + Opm::Parser parser; + auto deck = parser.parseString(deck_string, Opm::ParseContext()); + EclipseGrid grid1(10,10,10); + TableManager table ( deck ); + Eclipse3DProperties eclipseProperties ( deck , table, grid1); + Runspec runspec(deck); + + return Schedule(deck, grid1, eclipseProperties, runspec, ParseContext()); +} + + +BOOST_AUTO_TEST_CASE(TestActionAST_BASIC) { + // Missing comparator + BOOST_REQUIRE_THROW( ActionAST( {"WWCT", "OPX", "0.75"} ), std::invalid_argument); + + // Left hand side must be function expression + BOOST_REQUIRE_THROW( ActionAST({"0.75", "<", "1.0"}), std::invalid_argument); + + //Extra data + BOOST_REQUIRE_THROW(ActionAST({"0.75", "<", "1.0", "EXTRA"}), std::invalid_argument); + + ActionAST ast1({"WWCT", "OPX", ">", "0.75"}); + ActionAST ast2({"WWCT", "OPX", "=", "WWCT", "OPX"}); + ActionAST ast3({"WWCT", "OPY", ">", "0.75"}); + ActionContext context; + + context.add("WWCT", "OPX", 100); + BOOST_CHECK(ast1.eval(context)); + + context.add("WWCT", "OPX", -100); + BOOST_CHECK(!ast1.eval(context)); + + BOOST_CHECK(ast2.eval(context)); + BOOST_REQUIRE_THROW(ast3.eval(context), std::out_of_range); +} + +BOOST_AUTO_TEST_CASE(TestActionAST_OR_AND) { + ActionAST ast_or({"WWCT", "OPX", ">", "0.75", "OR", "WWCT", "OPY", ">", "0.75"}); + ActionAST ast_and({"WWCT", "OPX", ">", "0.75", "AND", "WWCT", "OPY", ">", "0.75"}); + ActionAST par({"WWCT", "OPX", ">", "0.75", "AND", "(", "WWCT", "OPY", ">", "0.75", "OR", "WWCT", "OPZ", ">", "0.75", ")"}); + ActionContext context; + + context.add("WWCT", "OPX", 100); + context.add("WWCT", "OPY", -100); + context.add("WWCT", "OPZ", 100); + BOOST_CHECK( ast_or.eval(context) ); + BOOST_CHECK( !ast_and.eval(context) ); + BOOST_CHECK( par.eval(context)); + + + context.add("WWCT", "OPX", -100); + context.add("WWCT", "OPY", 100); + context.add("WWCT", "OPZ", 100); + BOOST_CHECK( ast_or.eval(context) ); + BOOST_CHECK( !ast_and.eval(context) ); + BOOST_CHECK( !par.eval(context)); + + + context.add("WWCT", "OPX", 100); + context.add("WWCT", "OPY", 100); + context.add("WWCT", "OPZ", -100); + BOOST_CHECK( ast_or.eval(context) ); + BOOST_CHECK( ast_and.eval(context) ); + BOOST_CHECK( par.eval(context)); + + context.add("WWCT", "OPX", -100); + context.add("WWCT", "OPY", -100); + context.add("WWCT", "OPZ", -100); + BOOST_CHECK( !ast_or.eval(context) ); + BOOST_CHECK( !ast_and.eval(context) ); + BOOST_CHECK( !par.eval(context)); +} + +BOOST_AUTO_TEST_CASE(DATE) { + ActionAST ast({"MNTH", ">=", "JUN"}); + ActionContext context; + context.add("MNTH", 6); + BOOST_CHECK( ast.eval(context) ); + + context.add("MNTH", 8); + BOOST_CHECK( ast.eval(context) ); + + context.add("MNTH", 5); + BOOST_CHECK( !ast.eval(context) ); +} + + +BOOST_AUTO_TEST_CASE(MANUAL1) { + ActionAST ast({"GGPR", "FIELD", ">", "50000", "AND", "WGOR", "PR", ">" ,"GGOR", "FIELD"}); + ActionContext context; + + context.add("GGPR", "FIELD", 60000 ); + context.add("WGOR", "PR" , 300 ); + context.add("GGOR", "FIELD", 200); + BOOST_CHECK( ast.eval(context) ); + + context.add("GGPR", "FIELD", 0 ); + context.add("WGOR", "PR" , 300 ); + context.add("GGOR", "FIELD", 200); + BOOST_CHECK( !ast.eval(context) ); + + context.add("GGPR", "FIELD", 60000 ); + context.add("WGOR", "PR" , 100 ); + context.add("GGOR", "FIELD", 200); + BOOST_CHECK( !ast.eval(context) ); +} + +BOOST_AUTO_TEST_CASE(MANUAL2) { + ActionAST ast({"GWCT", "LIST1", ">", "0.70", "AND", "(", "GWPR", "LIST1", ">", "GWPR", "LIST2", "OR", "GWPR", "LIST1", ">", "GWPR", "LIST3", ")"}); + ActionContext context; + + context.add("GWCT", "LIST1", 1.0); + context.add("GWPR", "LIST1", 1 ); + context.add("GWPR", "LIST2", 2 ); + context.add("GWPR", "LIST3", 3 ); + BOOST_CHECK( !ast.eval(context)); + + context.add("GWCT", "LIST1", 1.0); + context.add("GWPR", "LIST1", 1 ); + context.add("GWPR", "LIST2", 2 ); + context.add("GWPR", "LIST3", 0 ); + BOOST_CHECK( ast.eval(context)); + + context.add("GWCT", "LIST1", 1.0); + context.add("GWPR", "LIST1", 1 ); + context.add("GWPR", "LIST2", 0 ); + context.add("GWPR", "LIST3", 3 ); + BOOST_CHECK( ast.eval(context)); + + context.add("GWCT", "LIST1", 1.0); + context.add("GWPR", "LIST1", 1 ); + context.add("GWPR", "LIST2", 0 ); + context.add("GWPR", "LIST3", 0 ); + BOOST_CHECK( ast.eval(context)); + + context.add("GWCT", "LIST1", 0.0); + context.add("GWPR", "LIST1", 1 ); + context.add("GWPR", "LIST2", 0 ); + context.add("GWPR", "LIST3", 3 ); + BOOST_CHECK( !ast.eval(context)); +} + +BOOST_AUTO_TEST_CASE(MANUAL3) { + ActionAST ast({"MNTH", ".GE.", "MAR", "AND", "MNTH", ".LE.", "OCT", "AND", "GMWL", "HIGH", ".GE.", "4"}); + ActionContext context; + + context.add("MNTH", 4); + context.add("GMWL", "HIGH", 4); + BOOST_CHECK( ast.eval(context)); + + context.add("MNTH", 3); + context.add("GMWL", "HIGH", 4); + BOOST_CHECK( ast.eval(context)); + + context.add("MNTH", 11); + context.add("GMWL", "HIGH", 4); + BOOST_CHECK( !ast.eval(context)); + + context.add("MNTH", 3); + context.add("GMWL", "HIGH", 3); + BOOST_CHECK( !ast.eval(context)); +} + + +BOOST_AUTO_TEST_CASE(MANUAL4) { + ActionAST ast({"GWCT", "FIELD", ">", "0.8", "AND", "DAY", ">", "1", "AND", "MNTH", ">", "JUN", "AND", "YEAR", ">=", "2021"}); + ActionContext context; + + + context.add("MNTH", 7); + context.add("DAY", 2); + context.add("YEAR", 2030); + context.add("GWCT", "FIELD", 1.0); + BOOST_CHECK( ast.eval(context) ); + + context.add("MNTH", 7); + context.add("DAY", 2); + context.add("YEAR", 2019); + context.add("GWCT", "FIELD", 1.0); + BOOST_CHECK( !ast.eval(context) ); +} + + + +BOOST_AUTO_TEST_CASE(MANUAL5) { + ActionAST ast({"WCG2", "PROD1", ">", "WCG5", "PROD2", "AND", "GCG3", "G1", ">", "GCG7", "G2", "OR", "FCG1", ">", "FCG7"}); + ActionContext context; + + context.add("WCG2", "PROD1", 100); + context.add("WCG5", "PROD2", 50); + context.add("GCG3", "G1", 200); + context.add("GCG7", "G2", 100); + context.add("FCG1", 100); + context.add("FCG7", 50); + BOOST_CHECK(ast.eval(context)); + + context.add("WCG2", "PROD1", 100); + context.add("WCG5", "PROD2", 50); + context.add("GCG3", "G1", 200); + context.add("GCG7", "G2", 100); + context.add("FCG1", 100); + context.add("FCG7", 150); + BOOST_CHECK(ast.eval(context)); + + context.add("WCG2", "PROD1", 100); + context.add("WCG5", "PROD2", 50); + context.add("GCG3", "G1", 20); + context.add("GCG7", "G2", 100); + context.add("FCG1", 100); + context.add("FCG7", 150); + BOOST_CHECK(!ast.eval(context)); + + context.add("WCG2", "PROD1", 100); + context.add("WCG5", "PROD2", 50); + context.add("GCG3", "G1", 20); + context.add("GCG7", "G2", 100); + context.add("FCG1", 200); + context.add("FCG7", 150); + BOOST_CHECK(ast.eval(context)); +} + + + +BOOST_AUTO_TEST_CASE(LGR) { + ActionAST ast({"LWCC" , "OPX", "LOCAL", "1", "2", "3", ">", "100"}); + ActionContext context; + + context.add("LWCC", "OPX:LOCAL:1:2:3", 200); + BOOST_CHECK(ast.eval(context)); + + context.add("LWCC", "OPX:LOCAL:1:2:3", 20); + BOOST_CHECK(!ast.eval(context)); +}