LCOV - code coverage report
Current view: top level - src/utils - CapabilityUtils.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 419b9d Lines: 142 171 83.0 %
Date: 2025-08-08 20:01:16 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        3209 : check(std::string requirements, const Registry & app_capabilities)
      21             : {
      22             :   using namespace peg;
      23             : 
      24             :   // unquote
      25             :   while (true)
      26             :   {
      27        4706 :     const auto len = requirements.length();
      28        7915 :     if (len >= 2 && ((requirements[0] == '\'' && requirements[len - 1] == '\'') ||
      29        3209 :                      (requirements[0] == '"' && requirements[len - 1] == '"')))
      30        1497 :       requirements = requirements.substr(1, len - 2);
      31             :     else
      32        3209 :       break;
      33        1497 :   }
      34        3209 :   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        3209 :   )");
      49             : 
      50        3209 :   if (!static_cast<bool>(parser))
      51           0 :     throw CapabilityException("Capabilities parser build failure.");
      52             : 
      53        6922 :   parser["Number"] = [](const SemanticValues & vs) { return vs.token_to_number<int>(); };
      54             : 
      55        3209 :   parser["Version"] = [](const SemanticValues & vs)
      56             :   {
      57        3713 :     switch (vs.choice())
      58             :     {
      59        2292 :       case 0: // Number '.' Version
      60             :       {
      61        2292 :         std::vector<int> ret{std::any_cast<int>(vs[0])};
      62        2292 :         const auto & vs1 = std::any_cast<std::vector<int>>(vs[1]);
      63        2292 :         ret.insert(ret.end(), vs1.begin(), vs1.end());
      64        2292 :         return ret;
      65        2292 :       }
      66             : 
      67        1421 :       case 1: // Number
      68        1421 :         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        3209 :   parser["LogicOperator"] = [](const SemanticValues & vs)
      82             :   {
      83         607 :     const auto op = vs.token();
      84         607 :     if (op == "&")
      85         603 :       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        3209 :   parser["Operator"] = [](const SemanticValues & vs)
     102             :   {
     103        1885 :     const auto op = vs.token();
     104        1885 :     if (op == "<=")
     105          13 :       return OP_LESS_EQ;
     106        1872 :     if (op == ">=")
     107        1204 :       return OP_GREATER_EQ;
     108         668 :     if (op == "<")
     109          52 :       return OP_LESS;
     110         616 :     if (op == ">")
     111          40 :       return OP_GREATER;
     112         576 :     if (op == "!=")
     113         307 :       return OP_NOT_EQ;
     114         269 :     if (op == "=" || op == "==")
     115         268 :       return OP_EQ;
     116           1 :     throw CapabilityException("Unknown operator '", op, "'.");
     117             :   };
     118             : 
     119        3667 :   parser["String"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
     120        7025 :   parser["Identifier"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
     121             : 
     122        3209 :   parser["Comparison"] = [&app_capabilities](const SemanticValues & vs)
     123             :   {
     124        1879 :     const auto left = std::any_cast<std::string>(vs[0]);
     125        1879 :     const auto op = std::any_cast<Operator>(vs[1]);
     126             : 
     127             :     // check existence
     128        1879 :     const auto it = app_capabilities.find(left);
     129        1879 :     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        1872 :     const auto & [app_value, doc] = it->second;
     136             : 
     137             :     // explicitly false causes any comparison to fail
     138        1872 :     if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
     139          20 :       return CheckState::CERTAIN_FAIL;
     140             : 
     141             :     // comparator
     142        1848 :     auto comp = [](int i, auto a, auto b)
     143             :     {
     144        1848 :       switch (i)
     145             :       {
     146          12 :         case OP_LESS_EQ:
     147          12 :           return a <= b;
     148        1204 :         case OP_GREATER_EQ:
     149        1204 :           return a >= b;
     150          45 :         case OP_LESS:
     151          45 :           return a < b;
     152          14 :         case OP_GREATER:
     153          14 :           return a > b;
     154         307 :         case OP_NOT_EQ:
     155         307 :           return a != b;
     156         266 :         case OP_EQ:
     157         266 :           return a == b;
     158             :       }
     159           0 :       return false;
     160             :     };
     161             : 
     162        1852 :     switch (vs.choice())
     163             :     {
     164        1394 :       case 0: // Identifier _ Operator _ Version
     165             :       {
     166             :         // int comparison
     167        1394 :         const auto right = std::any_cast<std::vector<int>>(vs[2]);
     168        1394 :         if (std::holds_alternative<int>(app_value))
     169             :         {
     170         168 :           if (right.size() != 1)
     171           1 :             throw CapabilityException("Expected an integer value in comparison");
     172             : 
     173         167 :           return comp(op, std::get<int>(app_value), right[0]) ? CheckState::CERTAIN_PASS
     174         167 :                                                               : CheckState::CERTAIN_FAIL;
     175             :         }
     176             : 
     177             :         // version comparison
     178        1226 :         std::vector<int> app_value_version;
     179             : 
     180        1226 :         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        2452 :         if (!MooseUtils::tokenizeAndConvert(
     186        1226 :                 std::get<std::string>(app_value), app_value_version, "."))
     187           2 :           throw CapabilityException("Expected a version number.");
     188             : 
     189             :         // compare versions
     190        2448 :         return comp(op, app_value_version, right) ? CheckState::CERTAIN_PASS
     191        1224 :                                                   : CheckState::CERTAIN_FAIL;
     192        1396 :       }
     193             : 
     194         458 :       case 1: // Identifier _ Operator _ String
     195             :       {
     196         458 :         const auto right = std::any_cast<std::string>(vs[2]);
     197             :         // the app value has to be a string
     198         458 :         if (!std::holds_alternative<std::string>(app_value))
     199           1 :           throw CapabilityException("Unexpected comparison to a string.");
     200             : 
     201         914 :         return comp(op, std::get<std::string>(app_value), MooseUtils::toLower(right))
     202         457 :                    ? CheckState::CERTAIN_PASS
     203         457 :                    : CheckState::CERTAIN_FAIL;
     204         458 :       }
     205             :     }
     206             : 
     207           0 :     throw CapabilityException("Failed comparison.");
     208        1879 :   };
     209             : 
     210        3209 :   parser["Bool"] = [&app_capabilities](const SemanticValues & vs)
     211             :   {
     212        4070 :     switch (vs.choice())
     213             :     {
     214        1945 :       case 0: // Comparison
     215             :       case 4: // '(' _ Expression _ ')'
     216        1945 :         return std::any_cast<CheckState>(vs[0]);
     217             : 
     218         189 :       case 1: // '!' Bool
     219         189 :         switch (std::any_cast<CheckState>(vs[0]))
     220             :         {
     221         130 :           case CheckState::CERTAIN_FAIL:
     222         130 :             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        1936 :       case 3: // Identifier
     247             :       {
     248        1936 :         const auto it = app_capabilities.find(std::any_cast<std::string>(vs[0]));
     249        1936 :         if (it != app_capabilities.end())
     250             :         {
     251        1907 :           const auto app_value = it->second.first;
     252        1907 :           if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
     253         105 :             return CheckState::CERTAIN_FAIL;
     254        1802 :           return CheckState::CERTAIN_PASS;
     255        1907 :         }
     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        3209 :   parser["Expression"] = [](const SemanticValues & vs)
     265             :   {
     266        3881 :     switch (vs.choice())
     267             :     {
     268         607 :       case 0: // Bool _ LogicOperator _ Expression
     269             :       {
     270         607 :         const auto left = std::any_cast<CheckState>(vs[0]);
     271         607 :         const auto op = std::any_cast<LogicOperator>(vs[1]);
     272         607 :         const auto right = std::any_cast<CheckState>(vs[2]);
     273             : 
     274         607 :         switch (op)
     275             :         {
     276         603 :           case OP_AND:
     277        2999 :             for (const auto state : {CheckState::CERTAIN_FAIL,
     278             :                                      CheckState::POSSIBLE_FAIL,
     279             :                                      CheckState::UNKNOWN,
     280             :                                      CheckState::POSSIBLE_PASS,
     281        3602 :                                      CheckState::CERTAIN_PASS})
     282        2999 :               if (left == state || right == state)
     283         603 :                 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        3274 :       case 1: // Bool
     302        3274 :         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        3209 :   parser.enable_packrat_parsing(); // Enable packrat parsing.
     311             : 
     312        3209 :   CheckState state = CheckState::CERTAIN_FAIL;
     313        3209 :   if (!parser.parse(requirements, state))
     314           8 :     throw CapabilityException("Unable to parse requested capabilities '", requirements, "'.");
     315             : 
     316        3196 :   std::string reason;
     317        3196 :   std::string doc;
     318             : 
     319        3196 :   return {state, reason, doc};
     320        3209 : }
     321             : } // namespace CapabilityUtils

Generated by: LCOV version 1.14