LCOV - code coverage report
Current view: top level - src/utils - CapabilityUtils.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 142 171 83.0 %
Date: 2025-07-17 01:28:37 Functions: 13 13 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "peglib.h"
      11             : 
      12             : #include "CapabilityUtils.h"
      13             : #include "MooseStringUtils.h"
      14             : #include <vector>
      15             : 
      16             : namespace CapabilityUtils
      17             : {
      18             : 
      19             : Result
      20        2964 : check(std::string requirements, const Registry & app_capabilities)
      21             : {
      22             :   using namespace peg;
      23             : 
      24             :   // unquote
      25             :   while (true)
      26             :   {
      27        4399 :     const auto len = requirements.length();
      28        7363 :     if (len >= 2 && ((requirements[0] == '\'' && requirements[len - 1] == '\'') ||
      29        2964 :                      (requirements[0] == '"' && requirements[len - 1] == '"')))
      30        1435 :       requirements = requirements.substr(1, len - 2);
      31             :     else
      32        2964 :       break;
      33        1435 :   }
      34        2964 :   if (requirements.length() == 0)
      35           0 :     return {CapabilityUtils::CERTAIN_PASS, "Empty requirements", ""};
      36             : 
      37             :   parser parser(R"(
      38             :     Expression    <-  _ Bool _ LogicOperator _ Expression / Bool _
      39             :     Bool          <-  Comparison / '!' Bool / '!' Identifier / Identifier / '(' _ Expression _ ')'
      40             :     Comparison    <-  Identifier _ Operator _ Version / Identifier _ Operator _ String
      41             :     String        <-  [a-zA-Z0-9_-]+
      42             :     Identifier    <-  [a-zA-Z][a-zA-Z0-9_]*
      43             :     Operator      <-  [<>=!]+
      44             :     LogicOperator <-  [&|]
      45             :     Version       <-  Number '.' Version  / Number
      46             :     Number        <-  [0-9]+
      47             :     ~_            <-  [ \t]*
      48        2964 :   )");
      49             : 
      50        2964 :   if (!static_cast<bool>(parser))
      51           0 :     throw CapabilityException("Capabilities parser build failure.");
      52             : 
      53        6540 :   parser["Number"] = [](const SemanticValues & vs) { return vs.token_to_number<int>(); };
      54             : 
      55        2964 :   parser["Version"] = [](const SemanticValues & vs)
      56             :   {
      57        3576 :     switch (vs.choice())
      58             :     {
      59        2206 :       case 0: // Number '.' Version
      60             :       {
      61        2206 :         std::vector<int> ret{std::any_cast<int>(vs[0])};
      62        2206 :         const auto & vs1 = std::any_cast<std::vector<int>>(vs[1]);
      63        2206 :         ret.insert(ret.end(), vs1.begin(), vs1.end());
      64        2206 :         return ret;
      65        2206 :       }
      66             : 
      67        1370 :       case 1: // Number
      68        1370 :         return std::vector<int>{std::any_cast<int>(vs[0])};
      69             : 
      70           0 :       default:
      71           0 :         throw CapabilityException("Unknown Number match");
      72             :     }
      73             :   };
      74             : 
      75             :   enum LogicOperator
      76             :   {
      77             :     OP_AND,
      78             :     OP_OR
      79             :   };
      80             : 
      81        2964 :   parser["LogicOperator"] = [](const SemanticValues & vs)
      82             :   {
      83         599 :     const auto op = vs.token();
      84         599 :     if (op == "&")
      85         595 :       return OP_AND;
      86           4 :     if (op == "|")
      87           4 :       return OP_OR;
      88           0 :     throw CapabilityException("Unknown logic operator.");
      89             :   };
      90             : 
      91             :   enum Operator
      92             :   {
      93             :     OP_LESS_EQ,
      94             :     OP_GREATER_EQ,
      95             :     OP_LESS,
      96             :     OP_GREATER,
      97             :     OP_NOT_EQ,
      98             :     OP_EQ
      99             :   };
     100             : 
     101        2964 :   parser["Operator"] = [](const SemanticValues & vs)
     102             :   {
     103        1805 :     const auto op = vs.token();
     104        1805 :     if (op == "<=")
     105          13 :       return OP_LESS_EQ;
     106        1792 :     if (op == ">=")
     107        1161 :       return OP_GREATER_EQ;
     108         631 :     if (op == "<")
     109          52 :       return OP_LESS;
     110         579 :     if (op == ">")
     111          40 :       return OP_GREATER;
     112         539 :     if (op == "!=")
     113         287 :       return OP_NOT_EQ;
     114         252 :     if (op == "=" || op == "==")
     115         251 :       return OP_EQ;
     116           1 :     throw CapabilityException("Unknown operator '", op, "'.");
     117             :   };
     118             : 
     119        3393 :   parser["String"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
     120        6527 :   parser["Identifier"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
     121             : 
     122        2964 :   parser["Comparison"] = [&app_capabilities](const SemanticValues & vs)
     123             :   {
     124        1799 :     const auto left = std::any_cast<std::string>(vs[0]);
     125        1799 :     const auto op = std::any_cast<Operator>(vs[1]);
     126             : 
     127             :     // check existence
     128        1799 :     const auto it = app_capabilities.find(left);
     129        1799 :     if (it == app_capabilities.end())
     130             :       // return an unknown if the capability does not exist, this is important as it
     131             :       // stays unknown upon negation
     132           7 :       return CheckState::UNKNOWN;
     133             : 
     134             :     // capability is registered by the app
     135        1792 :     const auto & [app_value, doc] = it->second;
     136             : 
     137             :     // explicitly false causes any comparison to fail
     138        1792 :     if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
     139          20 :       return CheckState::CERTAIN_FAIL;
     140             : 
     141             :     // comparator
     142        1768 :     auto comp = [](int i, auto a, auto b)
     143             :     {
     144        1768 :       switch (i)
     145             :       {
     146          12 :         case OP_LESS_EQ:
     147          12 :           return a <= b;
     148        1161 :         case OP_GREATER_EQ:
     149        1161 :           return a >= b;
     150          45 :         case OP_LESS:
     151          45 :           return a < b;
     152          14 :         case OP_GREATER:
     153          14 :           return a > b;
     154         287 :         case OP_NOT_EQ:
     155         287 :           return a != b;
     156         249 :         case OP_EQ:
     157         249 :           return a == b;
     158             :       }
     159           0 :       return false;
     160             :     };
     161             : 
     162        1772 :     switch (vs.choice())
     163             :     {
     164        1343 :       case 0: // Identifier _ Operator _ Version
     165             :       {
     166             :         // int comparison
     167        1343 :         const auto right = std::any_cast<std::vector<int>>(vs[2]);
     168        1343 :         if (std::holds_alternative<int>(app_value))
     169             :         {
     170         160 :           if (right.size() != 1)
     171           1 :             throw CapabilityException("Expected an integer value in comparison");
     172             : 
     173         159 :           return comp(op, std::get<int>(app_value), right[0]) ? CheckState::CERTAIN_PASS
     174         159 :                                                               : CheckState::CERTAIN_FAIL;
     175             :         }
     176             : 
     177             :         // version comparison
     178        1183 :         std::vector<int> app_value_version;
     179             : 
     180        1183 :         if (!std::holds_alternative<std::string>(app_value))
     181           0 :           throw CapabilityException(
     182           0 :               right.size() == 1 ? "Cannot compare capability " + left + " to a number."
     183           0 :                                 : "Cannot compare capability " + left + " to a version number.");
     184             : 
     185        2366 :         if (!MooseUtils::tokenizeAndConvert(
     186        1183 :                 std::get<std::string>(app_value), app_value_version, "."))
     187           2 :           throw CapabilityException("Expected a version number.");
     188             : 
     189             :         // compare versions
     190        2362 :         return comp(op, app_value_version, right) ? CheckState::CERTAIN_PASS
     191        1181 :                                                   : CheckState::CERTAIN_FAIL;
     192        1345 :       }
     193             : 
     194         429 :       case 1: // Identifier _ Operator _ String
     195             :       {
     196         429 :         const auto right = std::any_cast<std::string>(vs[2]);
     197             :         // the app value has to be a string
     198         429 :         if (!std::holds_alternative<std::string>(app_value))
     199           1 :           throw CapabilityException("Unexpected comparison to a string.");
     200             : 
     201         856 :         return comp(op, std::get<std::string>(app_value), MooseUtils::toLower(right))
     202         428 :                    ? CheckState::CERTAIN_PASS
     203         428 :                    : CheckState::CERTAIN_FAIL;
     204         429 :       }
     205             :     }
     206             : 
     207           0 :     throw CapabilityException("Failed comparison.");
     208        1799 :   };
     209             : 
     210        2964 :   parser["Bool"] = [&app_capabilities](const SemanticValues & vs)
     211             :   {
     212        3818 :     switch (vs.choice())
     213             :     {
     214        1865 :       case 0: // Comparison
     215             :       case 4: // '(' _ Expression _ ')'
     216        1865 :         return std::any_cast<CheckState>(vs[0]);
     217             : 
     218         190 :       case 1: // '!' Bool
     219         190 :         switch (std::any_cast<CheckState>(vs[0]))
     220             :         {
     221         131 :           case CheckState::CERTAIN_FAIL:
     222         131 :             return CheckState::CERTAIN_PASS;
     223          41 :           case CheckState::CERTAIN_PASS:
     224          41 :             return CheckState::CERTAIN_FAIL;
     225          14 :           case CheckState::POSSIBLE_FAIL:
     226          14 :             return CheckState::POSSIBLE_PASS;
     227           0 :           case CheckState::POSSIBLE_PASS:
     228           0 :             return CheckState::POSSIBLE_FAIL;
     229           4 :           default:
     230           4 :             return CheckState::UNKNOWN;
     231             :         }
     232             : 
     233           0 :       case 2: // '!' Identifier
     234             :       {
     235           0 :         const auto it = app_capabilities.find(std::any_cast<std::string>(vs[0]));
     236           0 :         if (it != app_capabilities.end())
     237             :         {
     238           0 :           const auto app_value = it->second.first;
     239           0 :           if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
     240           0 :             return CheckState::CERTAIN_PASS;
     241           0 :           return CheckState::CERTAIN_FAIL;
     242           0 :         }
     243           0 :         return CheckState::POSSIBLE_PASS;
     244             :       }
     245             : 
     246        1763 :       case 3: // Identifier
     247             :       {
     248        1763 :         const auto it = app_capabilities.find(std::any_cast<std::string>(vs[0]));
     249        1763 :         if (it != app_capabilities.end())
     250             :         {
     251        1734 :           const auto app_value = it->second.first;
     252        1734 :           if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
     253         106 :             return CheckState::CERTAIN_FAIL;
     254        1628 :           return CheckState::CERTAIN_PASS;
     255        1734 :         }
     256          29 :         return CheckState::POSSIBLE_FAIL;
     257             :       }
     258             : 
     259           0 :       default:
     260           0 :         throw CapabilityException("Unknown choice in Bool non-terminal");
     261             :     }
     262             :   };
     263             : 
     264        2964 :   parser["Expression"] = [](const SemanticValues & vs)
     265             :   {
     266        3628 :     switch (vs.choice())
     267             :     {
     268         599 :       case 0: // Bool _ LogicOperator _ Expression
     269             :       {
     270         599 :         const auto left = std::any_cast<CheckState>(vs[0]);
     271         599 :         const auto op = std::any_cast<LogicOperator>(vs[1]);
     272         599 :         const auto right = std::any_cast<CheckState>(vs[2]);
     273             : 
     274         599 :         switch (op)
     275             :         {
     276         595 :           case OP_AND:
     277        2959 :             for (const auto state : {CheckState::CERTAIN_FAIL,
     278             :                                      CheckState::POSSIBLE_FAIL,
     279             :                                      CheckState::UNKNOWN,
     280             :                                      CheckState::POSSIBLE_PASS,
     281        3554 :                                      CheckState::CERTAIN_PASS})
     282        2959 :               if (left == state || right == state)
     283         595 :                 return state;
     284           0 :             throw CapabilityException("Conjunction failure");
     285             : 
     286           4 :           case OP_OR:
     287          13 :             for (const auto state : {CheckState::CERTAIN_PASS,
     288             :                                      CheckState::POSSIBLE_PASS,
     289             :                                      CheckState::UNKNOWN,
     290             :                                      CheckState::POSSIBLE_FAIL,
     291          17 :                                      CheckState::CERTAIN_FAIL})
     292          13 :               if (left == state || right == state)
     293           4 :                 return state;
     294           0 :             throw CapabilityException("Conjunction failure");
     295             : 
     296           0 :           default:
     297           0 :             throw CapabilityException("Unknown logic operator");
     298             :         }
     299             :       }
     300             : 
     301        3029 :       case 1: // Bool
     302        3029 :         return std::any_cast<CheckState>(vs[0]);
     303             : 
     304           0 :       default:
     305           0 :         throw CapabilityException("Unknown choice in Expression non-terminal");
     306             :     }
     307             :   };
     308             : 
     309             :   // (4) Parse
     310        2964 :   parser.enable_packrat_parsing(); // Enable packrat parsing.
     311             : 
     312        2964 :   CheckState state = CheckState::CERTAIN_FAIL;
     313        2964 :   if (!parser.parse(requirements, state))
     314           8 :     throw CapabilityException("Unable to parse requested capabilities '", requirements, "'.");
     315             : 
     316        2951 :   std::string reason;
     317        2951 :   std::string doc;
     318             : 
     319        2951 :   return {state, reason, doc};
     320        2964 : }
     321             : } // namespace CapabilityUtils

Generated by: LCOV version 1.14