LCOV - code coverage report
Current view: top level - src/plugins/mathcheck - mathcheck.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 153 188 81.4 %
Date: 2019-09-12 12:28:41 Functions: 5 6 83.3 %

          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 (&regex, 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 (&regex, 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 (&regex);
     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 (&regex);
     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             : 

Generated by: LCOV version 1.13