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
|