https://mooseframework.inl.gov
CapabilityUtils.C
Go to the documentation of this file.
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 check(std::string requirements, const Registry & app_capabilities)
21 {
22  using namespace peg;
23 
24  // unquote
25  while (true)
26  {
27  const auto len = requirements.length();
28  if (len >= 2 && ((requirements[0] == '\'' && requirements[len - 1] == '\'') ||
29  (requirements[0] == '"' && requirements[len - 1] == '"')))
30  requirements = requirements.substr(1, len - 2);
31  else
32  break;
33  }
34  if (requirements.length() == 0)
35  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  )");
49 
50  if (!static_cast<bool>(parser))
51  throw CapabilityException("Capabilities parser build failure.");
52 
53  parser["Number"] = [](const SemanticValues & vs) { return vs.token_to_number<int>(); };
54 
55  parser["Version"] = [](const SemanticValues & vs)
56  {
57  switch (vs.choice())
58  {
59  case 0: // Number '.' Version
60  {
61  std::vector<int> ret{std::any_cast<int>(vs[0])};
62  const auto & vs1 = std::any_cast<std::vector<int>>(vs[1]);
63  ret.insert(ret.end(), vs1.begin(), vs1.end());
64  return ret;
65  }
66 
67  case 1: // Number
68  return std::vector<int>{std::any_cast<int>(vs[0])};
69 
70  default:
71  throw CapabilityException("Unknown Number match");
72  }
73  };
74 
75  enum LogicOperator
76  {
77  OP_AND,
78  OP_OR
79  };
80 
81  parser["LogicOperator"] = [](const SemanticValues & vs)
82  {
83  const auto op = vs.token();
84  if (op == "&")
85  return OP_AND;
86  if (op == "|")
87  return OP_OR;
88  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  parser["Operator"] = [](const SemanticValues & vs)
102  {
103  const auto op = vs.token();
104  if (op == "<=")
105  return OP_LESS_EQ;
106  if (op == ">=")
107  return OP_GREATER_EQ;
108  if (op == "<")
109  return OP_LESS;
110  if (op == ">")
111  return OP_GREATER;
112  if (op == "!=")
113  return OP_NOT_EQ;
114  if (op == "=" || op == "==")
115  return OP_EQ;
116  throw CapabilityException("Unknown operator '", op, "'.");
117  };
118 
119  parser["String"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
120  parser["Identifier"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
121 
122  parser["Comparison"] = [&app_capabilities](const SemanticValues & vs)
123  {
124  const auto left = std::any_cast<std::string>(vs[0]);
125  const auto op = std::any_cast<Operator>(vs[1]);
126 
127  // check existence
128  const auto it = app_capabilities.find(left);
129  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  return CheckState::UNKNOWN;
133 
134  // capability is registered by the app
135  const auto & [app_value, doc] = it->second;
136 
137  // explicitly false causes any comparison to fail
138  if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
140 
141  // comparator
142  auto comp = [](int i, auto a, auto b)
143  {
144  switch (i)
145  {
146  case OP_LESS_EQ:
147  return a <= b;
148  case OP_GREATER_EQ:
149  return a >= b;
150  case OP_LESS:
151  return a < b;
152  case OP_GREATER:
153  return a > b;
154  case OP_NOT_EQ:
155  return a != b;
156  case OP_EQ:
157  return a == b;
158  }
159  return false;
160  };
161 
162  switch (vs.choice())
163  {
164  case 0: // Identifier _ Operator _ Version
165  {
166  // int comparison
167  const auto right = std::any_cast<std::vector<int>>(vs[2]);
168  if (std::holds_alternative<int>(app_value))
169  {
170  if (right.size() != 1)
171  throw CapabilityException("Expected an integer value in comparison");
172 
173  return comp(op, std::get<int>(app_value), right[0]) ? CheckState::CERTAIN_PASS
175  }
176 
177  // version comparison
178  std::vector<int> app_value_version;
179 
180  if (!std::holds_alternative<std::string>(app_value))
181  throw CapabilityException(
182  right.size() == 1 ? "Cannot compare capability " + left + " to a number."
183  : "Cannot compare capability " + left + " to a version number.");
184 
186  std::get<std::string>(app_value), app_value_version, "."))
187  throw CapabilityException("Expected a version number.");
188 
189  // compare versions
190  return comp(op, app_value_version, right) ? CheckState::CERTAIN_PASS
192  }
193 
194  case 1: // Identifier _ Operator _ String
195  {
196  const auto right = std::any_cast<std::string>(vs[2]);
197  // the app value has to be a string
198  if (!std::holds_alternative<std::string>(app_value))
199  throw CapabilityException("Unexpected comparison to a string.");
200 
201  return comp(op, std::get<std::string>(app_value), MooseUtils::toLower(right))
204  }
205  }
206 
207  throw CapabilityException("Failed comparison.");
208  };
209 
210  parser["Bool"] = [&app_capabilities](const SemanticValues & vs)
211  {
212  switch (vs.choice())
213  {
214  case 0: // Comparison
215  case 4: // '(' _ Expression _ ')'
216  return std::any_cast<CheckState>(vs[0]);
217 
218  case 1: // '!' Bool
219  switch (std::any_cast<CheckState>(vs[0]))
220  {
229  default:
230  return CheckState::UNKNOWN;
231  }
232 
233  case 2: // '!' Identifier
234  {
235  const auto it = app_capabilities.find(std::any_cast<std::string>(vs[0]));
236  if (it != app_capabilities.end())
237  {
238  const auto app_value = it->second.first;
239  if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
242  }
244  }
245 
246  case 3: // Identifier
247  {
248  const auto it = app_capabilities.find(std::any_cast<std::string>(vs[0]));
249  if (it != app_capabilities.end())
250  {
251  const auto app_value = it->second.first;
252  if (std::holds_alternative<bool>(app_value) && std::get<bool>(app_value) == false)
255  }
257  }
258 
259  default:
260  throw CapabilityException("Unknown choice in Bool non-terminal");
261  }
262  };
263 
264  parser["Expression"] = [](const SemanticValues & vs)
265  {
266  switch (vs.choice())
267  {
268  case 0: // Bool _ LogicOperator _ Expression
269  {
270  const auto left = std::any_cast<CheckState>(vs[0]);
271  const auto op = std::any_cast<LogicOperator>(vs[1]);
272  const auto right = std::any_cast<CheckState>(vs[2]);
273 
274  switch (op)
275  {
276  case OP_AND:
277  for (const auto state : {CheckState::CERTAIN_FAIL,
279  CheckState::UNKNOWN,
282  if (left == state || right == state)
283  return state;
284  throw CapabilityException("Conjunction failure");
285 
286  case OP_OR:
287  for (const auto state : {CheckState::CERTAIN_PASS,
289  CheckState::UNKNOWN,
292  if (left == state || right == state)
293  return state;
294  throw CapabilityException("Conjunction failure");
295 
296  default:
297  throw CapabilityException("Unknown logic operator");
298  }
299  }
300 
301  case 1: // Bool
302  return std::any_cast<CheckState>(vs[0]);
303 
304  default:
305  throw CapabilityException("Unknown choice in Expression non-terminal");
306  }
307  };
308 
309  // (4) Parse
310  parser.enable_packrat_parsing(); // Enable packrat parsing.
311 
313  if (!parser.parse(requirements, state))
314  throw CapabilityException("Unable to parse requested capabilities '", requirements, "'.");
315 
316  std::string reason;
317  std::string doc;
318 
319  return {state, reason, doc};
320 }
321 } // namespace CapabilityUtils
std::string toLower(const std::string &name)
Convert supplied string to lower case.
bool tokenizeAndConvert(const std::string &str, std::vector< T > &tokenized_vector, const std::string &delimiter=" \\\)
tokenizeAndConvert splits a string using delimiter and then converts to type T.
CheckState
Return state for check.
std::tuple< CheckState, std::string, std::string > Result
Result from a capability check: the state, the reason, and the documentation.
The registry is used as a global singleton to collect information on all available MooseObject and Ac...
Definition: Registry.h:161
Result check(std::string requirements, const Registry &capabilities)
Checks if a set of requirements is satisified by the given capability registry.
Shared code for the Capabilities Registry and the python bindings to the Capabilities system...