Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for mathcheck plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 :
11 : #ifndef HAVE_KDBCONFIG
12 : #include "kdbconfig.h"
13 : #endif
14 :
15 : #include "floathelper.h"
16 : #include "mathcheck.h"
17 : #include <ctype.h>
18 : #include <kdberrors.h>
19 : #include <math.h>
20 : #include <regex.h>
21 : #include <stdio.h>
22 : #include <stdlib.h>
23 : #include <string.h>
24 :
25 : #define MIN_VALID_STACK 3
26 : #define EPSILON 0.00001
27 :
28 : typedef enum
29 : {
30 : ERROR = 0,
31 : ADD = 1,
32 : SUB = 2,
33 : MUL = 3,
34 : DIV = 4,
35 : NOT = 5,
36 : EQU = 6,
37 : LT = 7,
38 : GT = 8,
39 : LE = 9,
40 : GE = 10,
41 : RES = 11,
42 : VAL = 12,
43 : END = 13,
44 : SET = 14,
45 : NA = 15,
46 : EMPTY = 16
47 : } Operation;
48 : typedef struct
49 : {
50 : double value;
51 : Operation op;
52 : } PNElem;
53 :
54 :
55 37 : int elektraMathcheckGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey)
56 : {
57 37 : if (!strcmp (keyName (parentKey), "system/elektra/modules/mathcheck"))
58 : {
59 37 : KeySet * contract = ksNew (
60 : 30, keyNew ("system/elektra/modules/mathcheck", KEY_VALUE, "mathcheck plugin waits for your orders", KEY_END),
61 : keyNew ("system/elektra/modules/mathcheck/exports", KEY_END),
62 : keyNew ("system/elektra/modules/mathcheck/exports/get", KEY_FUNC, elektraMathcheckGet, KEY_END),
63 : keyNew ("system/elektra/modules/mathcheck/exports/set", KEY_FUNC, elektraMathcheckSet, KEY_END),
64 : #include ELEKTRA_README
65 : keyNew ("system/elektra/modules/mathcheck/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END),
66 : keyNew ("system/elektra/modules/mathcheck/export/constants", KEY_END),
67 : keyNew ("system/elektra/modules/mathcheck/export/constants/EPSILON", KEY_VALUE, ELEKTRA_STRINGIFY (EPSILON),
68 : KEY_END),
69 : KS_END);
70 37 : ksAppend (returned, contract);
71 37 : ksDel (contract);
72 :
73 37 : return 1; /* success */
74 : }
75 :
76 : return 1; /* success */
77 : }
78 : static PNElem nextVal (const PNElem * stackPtr)
79 : {
80 : PNElem * ptr = (PNElem *) stackPtr;
81 : PNElem result;
82 : result.op = ERROR;
83 704 : while (ptr->op != END)
84 : {
85 657 : if (ptr->op == VAL)
86 : {
87 159 : ptr->op = EMPTY;
88 159 : result.op = VAL;
89 159 : result.value = ptr->value;
90 : break;
91 : }
92 498 : else if (ptr->op == NA)
93 : {
94 22 : ptr->op = EMPTY;
95 22 : result.op = NA;
96 22 : result.value = 0;
97 : break;
98 : }
99 476 : ++ptr;
100 : }
101 : return result;
102 : }
103 47 : static PNElem doPrefixCalculation (PNElem * stack, PNElem * stackPtr)
104 : {
105 47 : --stackPtr;
106 : PNElem result;
107 : if (stackPtr < stack)
108 : {
109 : result.op = ERROR;
110 : }
111 295 : while (stackPtr >= stack)
112 : {
113 295 : if ((stackPtr->op == VAL || stackPtr->op == NA) && stackPtr != stack)
114 : {
115 181 : --stackPtr;
116 181 : continue;
117 : }
118 114 : else if ((stackPtr->op == VAL || stackPtr->op == NA) && stackPtr == stack)
119 : {
120 : break;
121 : }
122 : PNElem e1 = nextVal (stackPtr);
123 114 : PNElem e2 = nextVal (stackPtr);
124 114 : if (e1.op == NA)
125 : {
126 20 : if (stackPtr->op == ADD || stackPtr->op == SUB)
127 : {
128 : e1.value = 0;
129 : e1.op = VAL;
130 : }
131 6 : else if (stackPtr->op == DIV || stackPtr->op == MUL)
132 : {
133 6 : e1.value = 1;
134 6 : e1.op = VAL;
135 : }
136 : }
137 114 : if (e2.op == NA)
138 : {
139 2 : if (stackPtr->op == ADD || stackPtr->op == SUB)
140 : {
141 : e2.value = 0;
142 : e2.op = VAL;
143 : }
144 2 : else if (stackPtr->op == DIV || stackPtr->op == MUL)
145 : {
146 2 : e2.value = 1;
147 2 : e2.op = VAL;
148 : }
149 : }
150 114 : if (e1.op == VAL && e2.op == VAL)
151 : {
152 67 : switch (stackPtr->op)
153 : {
154 : case ADD:
155 53 : stackPtr->value = e1.value + e2.value;
156 53 : stackPtr->op = VAL;
157 53 : break;
158 : case SUB:
159 0 : stackPtr->value = e1.value - e2.value;
160 0 : stackPtr->op = VAL;
161 0 : break;
162 : case DIV:
163 12 : if (e2.value < EPSILON)
164 : {
165 0 : result.op = ERROR;
166 47 : return result;
167 : }
168 12 : stackPtr->value = e1.value / e2.value;
169 12 : stackPtr->op = VAL;
170 12 : break;
171 : case MUL:
172 2 : stackPtr->value = e1.value * e2.value;
173 2 : stackPtr->op = VAL;
174 2 : break;
175 : default:
176 : break;
177 : }
178 67 : result.op = stackPtr->op;
179 67 : result.value = stackPtr->value;
180 : }
181 : else
182 : {
183 47 : result.op = NA;
184 47 : return result;
185 : }
186 : }
187 0 : if (stackPtr->op != VAL)
188 : {
189 : result.op = ERROR;
190 : }
191 : else
192 : {
193 0 : result.op = RES;
194 : }
195 0 : result.value = stackPtr->value;
196 0 : return result;
197 : }
198 47 : static PNElem parsePrefixString (const char * prefixString, Key * curKey, KeySet * ks, Key * parentKey)
199 : {
200 47 : const char * regexString =
201 : "(((((\\.)|(\\.\\.\\/)*|(@)|(\\/))([[:alnum:]]*/)*[[:alnum:]]+))|('[0-9]*[.,]{0,1}[0-9]*')|(==)|([-+:/<>=!{*]))";
202 47 : char * ptr = (char *) prefixString;
203 : regex_t regex;
204 : Key * key;
205 :
206 47 : PNElem * stack = elektraMalloc (MIN_VALID_STACK * sizeof (PNElem));
207 :
208 47 : PNElem * stackPtr = stack;
209 : PNElem result;
210 47 : Operation resultOp = ERROR;
211 47 : result.op = ERROR;
212 : int ret;
213 47 : if ((ret = regcomp (®ex, regexString, REG_EXTENDED | REG_NEWLINE)))
214 : {
215 0 : ksDel (ks);
216 0 : return result;
217 : }
218 : regmatch_t match;
219 : char * searchKey = NULL;
220 : while (1)
221 247 : {
222 294 : stackPtr->op = ERROR;
223 294 : stackPtr->value = 0;
224 294 : int nomatch = regexec (®ex, ptr, 1, &match, 0);
225 294 : if (nomatch)
226 : {
227 : break;
228 : }
229 247 : int len = match.rm_eo - match.rm_so;
230 247 : int start = match.rm_so + (ptr - prefixString);
231 247 : if (!strncmp (prefixString + start, "==", 2))
232 : {
233 : resultOp = EQU;
234 : }
235 221 : else if (len == 1 && !isalpha (prefixString[start]) && prefixString[start] != '\'' && prefixString[start] != '.' &&
236 : prefixString[start] != '@')
237 : {
238 :
239 107 : switch (prefixString[start])
240 : {
241 :
242 : case '+':
243 53 : stackPtr->op = ADD;
244 53 : break;
245 : case '-':
246 0 : stackPtr->op = SUB;
247 0 : break;
248 : case '/':
249 12 : stackPtr->op = DIV;
250 12 : break;
251 : case '*':
252 2 : stackPtr->op = MUL;
253 2 : break;
254 : case ':':
255 : resultOp = SET;
256 : break;
257 : case '=':
258 19 : if (resultOp == LT)
259 : {
260 : resultOp = LE;
261 : }
262 19 : else if (resultOp == GT)
263 : {
264 : resultOp = GE;
265 : }
266 17 : else if (resultOp == ERROR)
267 : {
268 : resultOp = EQU;
269 : }
270 17 : else if (resultOp == EQU)
271 : {
272 : resultOp = EQU;
273 : }
274 17 : else if (resultOp == NOT)
275 : {
276 : resultOp = NOT;
277 : }
278 17 : else if (resultOp == SET)
279 : {
280 17 : resultOp = SET;
281 : }
282 : break;
283 : case '<':
284 2 : resultOp = LT;
285 2 : break;
286 : case '>':
287 2 : resultOp = GT;
288 2 : break;
289 : case '!':
290 0 : resultOp = NOT;
291 0 : break;
292 : default:
293 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "%c isn't a valid operation", prefixString[start]);
294 0 : regfree (®ex);
295 0 : if (searchKey)
296 : {
297 0 : elektraFree (searchKey);
298 : }
299 0 : elektraFree (stack);
300 0 : ksDel (ks);
301 0 : return result;
302 : break;
303 : }
304 : }
305 : else
306 : {
307 114 : char * subString = elektraMalloc (len + 1);
308 114 : strncpy (subString, prefixString + start, len);
309 114 : subString[len] = '\0';
310 114 : if (subString[0] == '\'' && subString[len - 1] == '\'')
311 31 : {
312 31 : subString[len - 1] = '\0';
313 31 : char * subPtr = (subString + 1);
314 31 : stackPtr->value = elektraEFtoF (subPtr);
315 31 : elektraFree (subString);
316 : }
317 : else
318 : {
319 83 : ksRewind (ks);
320 83 : if (subString[0] == '@')
321 : {
322 34 : searchKey = realloc (searchKey, len + 2 + strlen (keyName (parentKey)));
323 34 : strcpy (searchKey, keyName (parentKey));
324 34 : strcat (searchKey, "/");
325 34 : strcat (searchKey, subString + 2);
326 : }
327 49 : else if (subString[0] == '.')
328 : {
329 49 : searchKey = realloc (searchKey, len + 2 + strlen (keyName (curKey)));
330 49 : strcpy (searchKey, keyName (curKey));
331 49 : strcat (searchKey, "/");
332 49 : strcat (searchKey, subString);
333 : }
334 : else
335 : {
336 0 : searchKey = realloc (searchKey, len + 1);
337 0 : strcpy (searchKey, subString);
338 : }
339 83 : key = ksLookupByName (ks, searchKey, 0);
340 83 : if (!key)
341 : {
342 22 : stackPtr->value = 0;
343 22 : stackPtr->op = NA;
344 : }
345 : else
346 : {
347 61 : stackPtr->value = elektraEFtoF (keyString (key));
348 : }
349 83 : elektraFree (subString);
350 : }
351 114 : if (stackPtr->op != NA) stackPtr->op = VAL;
352 : }
353 247 : ++stackPtr;
354 247 : int offset = stackPtr - stack;
355 247 : stack = realloc (stack, (offset + 1) * sizeof (PNElem));
356 247 : stackPtr = stack;
357 247 : stackPtr += offset;
358 247 : ptr += match.rm_eo;
359 : }
360 47 : regfree (®ex);
361 47 : elektraFree (searchKey);
362 47 : ksDel (ks);
363 47 : stackPtr->op = END;
364 47 : result = doPrefixCalculation (stack, stackPtr);
365 47 : if (result.op != ERROR)
366 : {
367 : result.op = resultOp;
368 : }
369 : else
370 : {
371 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Not a valid Polish prefix notation syntax: %s\n", prefixString);
372 : }
373 47 : elektraFree (stack);
374 47 : return result;
375 : }
376 :
377 50 : int elektraMathcheckSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
378 : {
379 : Key * cur;
380 : PNElem result;
381 278 : while ((cur = ksNext (returned)) != NULL)
382 : {
383 185 : const Key * meta = keyGetMeta (cur, "check/math");
384 185 : if (!meta) continue;
385 : ELEKTRA_LOG_DEBUG ("Check key “%s” with value “%s”", keyName (cur), keyString (meta));
386 47 : result = parsePrefixString (keyString (meta), cur, ksDup (returned), parentKey);
387 : ELEKTRA_LOG_DEBUG ("Result: “%f”", result.value);
388 : char val1[MAX_CHARS_DOUBLE + 1]; // Include storage for trailing `\0` character
389 : char val2[MAX_CHARS_DOUBLE];
390 47 : strncpy (val1, keyString (cur), MAX_CHARS_DOUBLE);
391 47 : elektraFtoA (val2, sizeof (val2), result.value);
392 47 : if (result.op == ERROR)
393 : {
394 7 : return 1;
395 : }
396 47 : else if (result.op == EQU)
397 : {
398 26 : if (fabs (elektraEFtoF (keyString (cur)) - result.value) > EPSILON)
399 : {
400 5 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "Mathcheck failed: %s != %s", val1, val2);
401 5 : return -1;
402 : }
403 : }
404 21 : else if (result.op == NOT)
405 : {
406 0 : if (fabs (elektraEFtoF (keyString (cur)) - result.value) < EPSILON)
407 : {
408 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey,
409 : "Mathcheck failed: %s == %s but requirement was !=", val1, val2);
410 0 : return -1;
411 : }
412 : }
413 21 : else if (result.op == LT)
414 : {
415 2 : if (elektraEFtoF (keyString (cur)) >= result.value)
416 : {
417 2 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "Mathcheck failed: %s not < %s", val1, val2);
418 2 : return -1;
419 : }
420 : }
421 19 : else if (result.op == GT)
422 : {
423 0 : if (elektraEFtoF (keyString (cur)) <= result.value)
424 : {
425 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "Mathcheck failed: %s not > %s", val1, val2);
426 0 : return -1;
427 : }
428 : }
429 19 : else if (result.op == LE)
430 : {
431 0 : if (elektraEFtoF (keyString (cur)) > result.value)
432 : {
433 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "Mathcheck failed: %s not <= %s", val1, val2);
434 0 : return -1;
435 : }
436 : }
437 19 : else if (result.op == GE)
438 : {
439 2 : if (elektraEFtoF (keyString (cur)) < result.value)
440 : {
441 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "Mathcheck failed: %s not >= %s", val1, val2);
442 0 : return -1;
443 : }
444 : }
445 17 : else if (result.op == SET)
446 : {
447 : ELEKTRA_LOG_DEBUG ("Set value of “%s” to “%s”", keyName (cur), val2);
448 17 : keySetString (cur, val2);
449 : }
450 : }
451 : return 1; /* success */
452 : }
453 :
454 147 : Plugin * ELEKTRA_PLUGIN_EXPORT
455 : {
456 : // clang-format off
457 147 : return elektraPluginExport("mathcheck",
458 : ELEKTRA_PLUGIN_GET, &elektraMathcheckGet,
459 : ELEKTRA_PLUGIN_SET, &elektraMathcheckSet,
460 : ELEKTRA_PLUGIN_END);
461 : }
462 :
|