https://mooseframework.inl.gov
Loading...
Searching...
No Matches
CapabilityRegistry.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 "CapabilityRegistry.h"
11
12#include "CapabilityException.h"
13#include "MooseStringUtils.h"
14
15#include "peglib.h"
16
17#include <regex>
18#include <set>
19#include <utility>
20
21namespace Moose::internal
22{
23
24const std::set<std::string, std::less<>> CapabilityRegistry::augmented_capability_names{
25 // TestHarness.getCapabilities()
26 "hpc",
27 "machine",
28 "library_mode",
29 // TestHarness.testers.RunApp.getAugmentedCapabilities()
30 "mpi_procs",
31 "num_threads"};
32
33Capability &
34CapabilityRegistry::add(const std::string_view name,
35 const Capability::Value & value,
36 const std::string_view doc)
37{
38 auto it_pair = _registry.lower_bound(name);
39 if (it_pair != _registry.end() && it_pair->first == name)
40 {
41 auto & capability = it_pair->second;
42 if (capability.getValue() != value || capability.getDoc() != doc)
43 throw CapabilityException("Capability '" + std::string(name) +
44 "' already exists and is not equal");
45 return capability;
46 }
47
48 return _registry
49 .emplace_hint(it_pair,
50 std::piecewise_construct,
51 std::forward_as_tuple(name),
52 std::forward_as_tuple(name, value, doc))
53 ->second;
54}
55
56const Capability *
57CapabilityRegistry::query(std::string capability) const
58{
59 capability = MooseUtils::toLower(capability);
60 if (const auto it = _registry.find(capability); it != _registry.end())
61 return &it->second;
62 return nullptr;
63}
64
65const Capability &
66CapabilityRegistry::get(const std::string & capability) const
67{
68 if (const auto capability_ptr = query(capability))
69 return *capability_ptr;
70 throw CapabilityException("Capability '" + capability + "' not registered");
71}
72
73[[noreturn]] void
74checkException(const peg::SemanticValues & vs,
75 const std::string & message,
76 const std::optional<Capability> capability = {})
77{
78 std::string msg = "Capability statement '" + vs.token_to_string() + "': ";
79 if (capability)
80 msg += "capability '" + capability->toString() + "' ";
81 msg += message;
82 throw CapabilityException(msg);
83}
84
85CapabilityRegistry::CheckResult
86CapabilityRegistry::check(std::string requirements,
88 options /* = CapabilityRegistry::CheckOptions() */) const
89{
90 using namespace peg;
91
92 // unquote
93 while (true)
94 {
95 const auto len = requirements.length();
96 if (len >= 2 && ((requirements[0] == '\'' && requirements[len - 1] == '\'') ||
97 (requirements[0] == '"' && requirements[len - 1] == '"')))
98 requirements = requirements.substr(1, len - 2);
99 else
100 break;
101 }
102
103 CheckResult result;
105
106 if (requirements.length() == 0)
107 {
109 return result;
110 }
111
112 static parser parser(R"(
113 Expression <- _ Bool _ LogicOperator _ Expression / Bool _
114 Bool <- Comparison / '!' Bool / '!' Identifier / Identifier / '(' _ Expression _ ')'
115 Comparison <- Identifier _ Operator _ Version / Identifier _ Operator _ String
116 String <- [a-zA-Z0-9_-]+
117 Identifier <- [a-zA-Z][a-zA-Z0-9_]*
118 Operator <- [<>=!]+
119 LogicOperator <- [&|]
120 Version <- Number '.' Version / Number
121 Number <- [0-9]+
122 ~_ <- [ \t]*
123 )");
124
125 if (!static_cast<bool>(parser))
126 throw CapabilityException("Capabilities parser build failure.");
127
128 // Keep track of unknown capabilities in the event that
129 // the check must be certain
130 std::set<std::string> unknown_capabilities;
131 const auto add_unknown_capability = [&unknown_capabilities](const auto & name)
132 { unknown_capabilities.insert(MooseUtils::toLower(name)); };
133
134 // Make sure that the capabilities to ignore are valid capabilities
135 for (const auto & name : options.ignore_capabilities)
136 if (!_registry.count(name))
137 throw CapabilityException("Capability to ignore '" + name + "' is not known");
138
139 parser["Number"] = [](const SemanticValues & vs) { return vs.token_to_number<int>(); };
140
141 parser["Version"] = [](const SemanticValues & vs)
142 {
143 switch (vs.choice())
144 {
145 case 0: // Number '.' Version
146 {
147 std::vector<int> ret{std::any_cast<int>(vs[0])};
148 const auto & vs1 = std::any_cast<std::vector<int>>(vs[1]);
149 ret.insert(ret.end(), vs1.begin(), vs1.end());
150 return ret;
151 }
152
153 case 1: // Number
154 return std::vector<int>{std::any_cast<int>(vs[0])};
155 }
156
157 checkException(vs, "unknown number match.");
158 };
159
160 enum LogicOperator
161 {
162 OP_AND,
163 OP_OR
164 };
165
166 parser["LogicOperator"] = [](const SemanticValues & vs)
167 {
168 const auto op = vs.token();
169 if (op == "&")
170 return OP_AND;
171 if (op == "|")
172 return OP_OR;
173 checkException(vs, "unknown logic operator.");
174 };
175
176 enum Operator
177 {
178 OP_LESS_EQ,
179 OP_GREATER_EQ,
180 OP_LESS,
181 OP_GREATER,
182 OP_NOT_EQ,
183 OP_EQ
184 };
185
186 parser["Operator"] = [](const SemanticValues & vs)
187 {
188 const auto op = vs.token();
189 if (op == "<=")
190 return OP_LESS_EQ;
191 if (op == ">=")
192 return OP_GREATER_EQ;
193 if (op == "<")
194 return OP_LESS;
195 if (op == ">")
196 return OP_GREATER;
197 if (op == "!=")
198 return OP_NOT_EQ;
199 if (op == "=" || op == "==")
200 return OP_EQ;
201 checkException(vs, "unknown operator.");
202 };
203
204 parser["String"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
205 parser["Identifier"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
206
207 parser["Comparison"] =
208 [this, &add_unknown_capability, &options, &result](const SemanticValues & vs)
209 {
210 const auto left = std::any_cast<std::string>(vs[0]);
211 const auto op = std::any_cast<Operator>(vs[1]);
212
213 // check existence
214 const auto capability_ptr = query(left);
215 if (!capability_ptr)
216 {
217 // return an unknown if the capability does not exist, this is important as it
218 // stays unknown upon negation
219 add_unknown_capability(left);
220 return CheckState::UNKNOWN;
221 }
222
223 // capability is registered by the app
224 const auto & capability = *capability_ptr;
225 const auto & name = capability.getName();
226
227 // register capability as seen
228 result.capability_names.insert(name);
229
230 // whether or not the capability is ignored
231 const auto is_ignored = [&name, &options]() { return options.ignore_capabilities.count(name); };
232
233 // explicitly false causes any comparison to fail unless ignored
234 if (const auto bool_ptr = capability.queryBoolValue(); (bool_ptr && !(*bool_ptr)))
235 return is_ignored() ? CheckState::IGNORE : CheckState::CERTAIN_FAIL;
236
237 // comparator
238 auto comp = [&is_ignored](const int i, const auto & a, const auto & b)
239 {
240 // early exit for ignored capabilities
241 if (is_ignored())
242 return CheckState::IGNORE;
243
244 // do the comparison
245 const auto do_comp = [&i, &a, &b]()
246 {
247 switch (i)
248 {
249 case OP_LESS_EQ:
250 return a <= b;
251 case OP_GREATER_EQ:
252 return a >= b;
253 case OP_LESS:
254 return a < b;
255 case OP_GREATER:
256 return a > b;
257 case OP_NOT_EQ:
258 return a != b;
259 case OP_EQ:
260 return a == b;
261 }
262 return false;
263 };
265 };
266
267 // version comparison
268 std::vector<int> app_value_version;
269
270 switch (vs.choice())
271 {
272 case 0: // Identifier _ Operator _ Version
273 {
274 // int comparison
275 const auto right = std::any_cast<std::vector<int>>(vs[2]);
276 if (const auto int_ptr = capability.queryIntValue())
277 {
278 if (right.size() != 1)
279 checkException(vs, "cannot be compared to a version.", capability);
280
281 return comp(op, *int_ptr, right[0]);
282 }
283
284 const auto string_ptr = capability.queryStringValue();
285 if (!string_ptr)
287 "cannot be compared to a " +
288 std::string(right.size() == 1 ? "number" : "version number") + ".",
289 capability);
290
291 if (!MooseUtils::tokenizeAndConvert(*string_ptr, app_value_version, "."))
292 checkException(vs, "cannot be compared to a version.", capability);
293
294 // compare versions
295 return comp(op, app_value_version, right);
296 }
297
298 case 1: // Identifier _ Operator _ String
299 {
300 // here we would check for valid options and throw if not valid
301 const auto right = MooseUtils::toLower(std::any_cast<std::string>(vs[2]));
302 // the capability value has to be a string
303 const auto string_ptr = capability.queryStringValue();
304 if (!string_ptr)
305 checkException(vs, "cannot be compared to a string.", capability);
306
307 // If this capability has an enumeration, make sure a valid
308 // choice is used
309 if (!capability.hasEnumeration(right))
311 "'" + right + "' invalid for capability '" + left +
312 "'; valid values: " + capability.enumerationToString());
313
314 // Capability is a version
315 if (MooseUtils::tokenizeAndConvert(*string_ptr, app_value_version, "."))
316 checkException(vs, "cannot be compared to a string.", capability);
317
318 return comp(op, *string_ptr, right);
319 }
320 }
321
322 checkException(vs, "failed comparison.", capability);
323 };
324
325 parser["Bool"] = [this, &add_unknown_capability, &options, &result](const SemanticValues & vs)
326 {
327 switch (vs.choice())
328 {
329 case 0: // Comparison
330 case 4: // '(' _ Expression _ ')'
331 return std::any_cast<CheckState>(vs[0]);
332
333 case 1: // '!' Bool
334 switch (std::any_cast<CheckState>(vs[0]))
335 {
345 return CheckState::IGNORE;
346 default:
347 return CheckState::UNKNOWN;
348 }
349
350 case 2: // '!' Identifier
351 case 3: // Identifier
352 {
353 const bool negated = vs.choice() == 2;
354 const auto identifier = std::any_cast<std::string>(vs[0]);
355 if (const auto capability_ptr = query(identifier))
356 {
357 const auto & capability = *capability_ptr;
358 const auto & name = capability.getName();
359
360 // explicit; cannot be a bool expression
361 if (capability.getExplicit())
362 {
363 std::string message = "capability '" + name +
364 "' requires a value and cannot be used in a boolean expression";
365 if (capability.queryEnumeration())
366 message += "; valid values: " + capability.enumerationToString();
367 checkException(vs, message);
368 }
369
370 // mark as used
371 result.capability_names.insert(name);
372 // is ignored
373 if (options.ignore_capabilities.count(name))
374 return CheckState::IGNORE;
375
376 // helper for negating a passing value if needed
377 const auto bool_to_pass = [&negated](const bool val)
378 { return (val ^ negated) ? CheckState::CERTAIN_FAIL : CheckState::CERTAIN_PASS; };
379 // has a boolean value, so use it
380 if (const auto bool_ptr = capability.queryBoolValue())
381 return bool_to_pass(!*bool_ptr);
382 // not ignored and doesn't have a boolean value
383 return bool_to_pass(false);
384 }
385
386 add_unknown_capability(identifier);
388 }
389
390 default:
391 throw CapabilityException("Unknown choice in Bool non-terminal");
392 }
393 };
394
395 parser["Expression"] = [](const SemanticValues & vs)
396 {
397 switch (vs.choice())
398 {
399 case 0: // Bool _ LogicOperator _ Expression
400 {
401 const auto left = std::any_cast<CheckState>(vs[0]);
402 const auto right = std::any_cast<CheckState>(vs[2]);
403 const auto op = std::any_cast<LogicOperator>(vs[1]);
404
405 switch (op)
406 {
407 case OP_AND:
408 if (left == CheckState::IGNORE)
409 return right;
410 if (right == CheckState::IGNORE)
411 return left;
412 for (const auto state : {CheckState::CERTAIN_FAIL,
417 if (left == state || right == state)
418 return state;
419 throw CapabilityException("Conjunction failure");
420
421 case OP_OR:
422 if (left == CheckState::IGNORE || right == CheckState::IGNORE)
423 return CheckState::IGNORE;
424 for (const auto state : {CheckState::CERTAIN_PASS,
429 if (left == state || right == state)
430 return state;
431 throw CapabilityException("Conjunction failure");
432
433 default:
434 throw CapabilityException("Unknown logic operator");
435 }
436 }
437
438 case 1: // Bool
439 return std::any_cast<CheckState>(vs[0]);
440
441 default:
442 throw CapabilityException("Unknown choice in Expression non-terminal");
443 }
444 };
445
446 // (4) Parse
447 parser.enable_packrat_parsing(); // Enable packrat parsing.
448
449 if (!parser.parse(requirements, result.state))
450 throw CapabilityException("Unable to parse requested capabilities '", requirements, "'.");
451
452 // If certain and unknown capabilities were found, throw accordingly
453 if (options.certain && unknown_capabilities.size())
454 throw UnknownCapabilitiesException({unknown_capabilities.begin(), unknown_capabilities.end()});
455
456 // Consider an ignored state to be a pass
457 if (result.state == CheckState::IGNORE)
459
460 return result;
461}
462} // namespace CapabilityUtils
Common execption to be thrown when interacting with capabilities.
An entry for a single capability.
Definition Capability.h:30
std::variant< bool, int, std::string > Value
A capability can have a bool, int, or string value.
Definition Capability.h:33
Exception thrown when capabilities are unknown.
CheckResult check(std::string requirements, const CapabilityRegistry::CheckOptions &options=CapabilityRegistry::CheckOptions()) const
Checks if a set of requirements is satisified by the capabilities.
const Capability * query(std::string capability) const
Query a capability, if it exists, otherwise nullptr.
static const std::set< std::string, std::less<> > augmented_capability_names
Capabilities that are reserved and can only be augmented.
RegistryType _registry
Registry storage.
Capability & add(const std::string_view name, const Moose::Capability::Value &value, const std::string_view doc)
Add a capability.
const Capability & get(const std::string &capability) const
Get a capability.
bool tokenizeAndConvert(const std::string &str, std::vector< T > &tokenized_vector, const std::string &delimiter=" \t\n\v\f\r")
tokenizeAndConvert splits a string using delimiter and then converts to type T.
std::string toLower(std::string name)
Convert supplied string to lower case.
void checkException(const peg::SemanticValues &vs, const std::string &message, const std::optional< Capability > capability={})
std::set< std::string > ignore_capabilities
Capabilities to ignore; checks using them will always pass.
bool certain
Whether or not all capabilities must be known.
std::set< std::string > capability_names
The capability names that existed in the check string.