LCOV - code coverage report
Current view: top level - src - canonical.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 111 111 100.0 %
Date: 2026-07-14 21:28:07 Functions: 11 11 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 90 90 100.0 %

           Branch data     Line data    Source code
       1                 :            : // Copyright 2019-2026 David Robillard <d@drobilla.net>
       2                 :            : // SPDX-License-Identifier: ISC
       3                 :            : 
       4                 :            : #include "char_utils.h"
       5                 :            : #include "read_utils.h"
       6                 :            : #include "result.h"
       7                 :            : #include "write_utils.h"
       8                 :            : 
       9                 :            : #include <exess/exess.h>
      10                 :            : 
      11                 :            : #include <stdbool.h>
      12                 :            : #include <stddef.h>
      13                 :            : #include <stdint.h>
      14                 :            : 
      15                 :            : /// Return true iff `c` is "0"
      16                 :            : static inline EXESS_NONBLOCKING bool
      17                 :        273 : is_zero(const int c)
      18                 :            : {
      19                 :        273 :   return c == '0';
      20                 :            : }
      21                 :            : 
      22                 :            : /// Return true iff `c` is "."
      23                 :            : static inline EXESS_NONBLOCKING bool
      24                 :         50 : is_point(const int c)
      25                 :            : {
      26                 :         50 :   return c == '.';
      27                 :            : }
      28                 :            : 
      29                 :            : // Scan forwards as long as `pred` returns true for characters
      30                 :            : static inline EXESS_NONBLOCKING size_t
      31                 :        272 : scan(bool EXESS_NONBLOCKING (*pred)(const int), const char* const str, size_t i)
      32                 :            : {
      33         [ +  + ]:       1579 :   while (pred(str[i])) {
      34                 :       1307 :     ++i;
      35                 :            :   }
      36                 :            : 
      37                 :        272 :   return i;
      38                 :            : }
      39                 :            : 
      40                 :            : // Skip the next character if `pred` returns true for it
      41                 :            : static inline EXESS_NONBLOCKING size_t
      42                 :        168 : skip(bool EXESS_NONBLOCKING (*pred)(const int),
      43                 :            :      const char* const str,
      44                 :            :      const size_t      i)
      45                 :            : {
      46         [ +  + ]:        168 :   return i + (pred(str[i]) ? 1 : 0);
      47                 :            : }
      48                 :            : 
      49                 :            : static EXESS_NONBLOCKING ExessVariableResult
      50                 :         54 : write_decimal(const char* const str, const size_t buf_size, char* const buf)
      51                 :            : {
      52                 :         54 :   const size_t sign    = skip_whitespace(str);     // Sign
      53                 :         54 :   const size_t leading = skip(is_sign, str, sign); // First digit
      54   [ +  +  +  + ]:         54 :   if (str[leading] != '.' && !is_digit(str[leading])) {
      55                 :          4 :     return VRESULT(EXESS_EXPECTED_DIGIT, sign, 0);
      56                 :            :   }
      57                 :            : 
      58                 :         50 :   const size_t first = scan(is_zero, str, leading); // First non-zero
      59                 :         50 :   const size_t point = scan(is_digit, str, first);  // Decimal point
      60                 :            :   const size_t end =
      61                 :         50 :     scan(is_digit, str, skip(is_point, str, point)); // Last digit
      62                 :            : 
      63                 :            :   // Ignore trailing zeros
      64                 :         50 :   size_t last = end;
      65         [ +  + ]:         50 :   if (str[point] == '.') {
      66         [ +  + ]:         36 :     while (str[last - 1] == '0') {
      67                 :         18 :       --last;
      68                 :            :     }
      69                 :            :   }
      70                 :            : 
      71                 :            :   // Add leading sign only if the number is negative
      72                 :         50 :   size_t o = 0;
      73         [ +  + ]:         50 :   if (str[sign] == '-') {
      74                 :         20 :     o += write_char('-', buf_size, buf, o);
      75                 :            :   }
      76                 :            : 
      77                 :            :   // Handle zero as a special case (no non-zero digits to copy)
      78         [ +  + ]:         50 :   if (first == last) {
      79                 :          8 :     o += write_string(3, "0.0", buf_size, buf, o);
      80                 :          8 :     return VRESULT(EXESS_SUCCESS, end, o);
      81                 :            :   }
      82                 :            : 
      83                 :            :   // Add leading zero if needed to have at least one digit before the point
      84         [ +  + ]:         42 :   if (str[first] == '.') {
      85                 :          6 :     o += write_char('0', buf_size, buf, o);
      86                 :            :   }
      87                 :            : 
      88                 :            :   // Add digits
      89                 :         42 :   o += write_string(last - first, str + first, buf_size, buf, o);
      90                 :            : 
      91         [ +  + ]:         42 :   if (str[point] != '.') {
      92                 :            :     // Add missing decimal suffix
      93                 :         24 :     o += write_string(2, ".0", buf_size, buf, o);
      94         [ +  + ]:         18 :   } else if (point == last - 1) {
      95                 :            :     // Add missing trailing zero after point
      96                 :          8 :     o += write_char('0', buf_size, buf, o);
      97                 :            :   }
      98                 :            : 
      99                 :         42 :   return VRESULT(EXESS_SUCCESS, end, o);
     100                 :            : }
     101                 :            : 
     102                 :            : static ExessVariableResult
     103                 :         71 : write_integer(const ExessDatatype datatype,
     104                 :            :               const char* const   str,
     105                 :            :               const size_t        buf_size,
     106                 :            :               char* const         buf)
     107                 :            : {
     108                 :         71 :   const size_t sign = skip_whitespace(str); // Sign
     109                 :            : 
     110   [ +  +  +  +  :         71 :   if ((str[sign] == '-' && (datatype == EXESS_NON_NEGATIVE_INTEGER ||
                   +  + ]
     111                 :         69 :                             datatype == EXESS_POSITIVE_INTEGER)) ||
     112   [ +  +  +  +  :         69 :       (str[sign] == '+' && (datatype == EXESS_NON_POSITIVE_INTEGER ||
                   +  + ]
     113                 :         67 :                             datatype == EXESS_NEGATIVE_INTEGER)) ||
     114   [ +  +  +  + ]:         67 :       (str[sign] != '-' && (datatype == EXESS_NEGATIVE_INTEGER))) {
     115                 :          7 :     return VRESULT(EXESS_BAD_VALUE, sign, 0);
     116                 :            :   }
     117                 :            : 
     118                 :         64 :   const size_t leading = skip(is_sign, str, sign); // First digit
     119         [ +  + ]:         64 :   if (!is_digit(str[leading])) {
     120                 :          3 :     return VRESULT(EXESS_EXPECTED_DIGIT, leading, 0);
     121                 :            :   }
     122                 :            : 
     123                 :         61 :   const size_t first = scan(is_zero, str, leading); // First non-zero
     124                 :         61 :   const size_t last  = scan(is_digit, str, first);  // Last digit
     125                 :            : 
     126                 :            :   // Handle zero as a special case (no non-zero digits to copy)
     127                 :         61 :   size_t o = 0;
     128         [ +  + ]:         61 :   if (first == last) {
     129         [ +  + ]:         12 :     if (datatype == EXESS_POSITIVE_INTEGER) {
     130                 :          2 :       return VRESULT(EXESS_BAD_VALUE, sign, 0);
     131                 :            :     }
     132                 :            : 
     133                 :         10 :     o += write_char('0', buf_size, buf, o);
     134                 :         10 :     return VRESULT(EXESS_SUCCESS, first, o);
     135                 :            :   }
     136                 :            : 
     137                 :            :   // Add leading sign only if the number is negative
     138                 :         49 :   ExessStatus st = EXESS_SUCCESS;
     139         [ +  + ]:         49 :   if (str[sign] == '-') {
     140                 :         18 :     o += write_char('-', buf_size, buf, o);
     141         [ +  + ]:         31 :   } else if (datatype == EXESS_NON_POSITIVE_INTEGER) {
     142                 :          1 :     return VRESULT(EXESS_BAD_VALUE, first, sign);
     143                 :            :   }
     144                 :            : 
     145                 :            :   // Add digits
     146                 :         48 :   o += write_string(last - first, str + first, buf_size, buf, o);
     147                 :            : 
     148                 :         48 :   return VRESULT(st, last, o);
     149                 :            : }
     150                 :            : 
     151                 :            : static EXESS_NONBLOCKING ExessVariableResult
     152                 :         21 : write_date_time(const char* const str, const size_t buf_size, char* const buf)
     153                 :            : {
     154                 :         21 :   EXESS_CONSTEXPR ExessDuration zero = {0, 0, 0};
     155                 :            : 
     156                 :         21 :   ExessDateTime     value = {0, 0, 0, 0, 0, 0, 0, 0};
     157                 :         21 :   const ExessResult r     = exess_read_date_time(str, &value);
     158         [ +  + ]:         21 :   if (r.status) {
     159                 :          1 :     return VRESULT(r.status, r.count, 0);
     160                 :            :   }
     161                 :            : 
     162                 :            :   // Wrap 24:00 midnight to 00:00 on the next day
     163                 :         20 :   const ExessResult w = exess_write_date_time(
     164         [ +  + ]:         20 :     (value.hour == 24) ? exess_add_date_time_duration(value, zero) : value,
     165                 :            :     buf_size,
     166                 :            :     buf);
     167                 :            : 
     168                 :         20 :   return VRESULT(w.status, r.count, w.count);
     169                 :            : }
     170                 :            : 
     171                 :            : static ExessVariableResult
     172                 :          9 : write_hex(const char* const str, const size_t buf_size, char* const buf)
     173                 :            : {
     174                 :          9 :   size_t i = skip_whitespace(str);
     175                 :          9 :   size_t o = 0;
     176                 :            : 
     177         [ +  + ]:         48 :   for (; str[i]; ++i) {
     178         [ +  + ]:         41 :     if (is_hexdig(str[i])) {
     179                 :         39 :       o += write_char(str[i], buf_size, buf, o);
     180                 :            :     } else {
     181                 :          2 :       break;
     182                 :            :     }
     183                 :            :   }
     184                 :            : 
     185   [ +  +  +  + ]:          9 :   return VRESULT(
     186                 :            :     (o == 0 || o % 2 != 0) ? EXESS_EXPECTED_HEX : EXESS_SUCCESS, i, o);
     187                 :            : }
     188                 :            : 
     189                 :            : static ExessVariableResult
     190                 :          7 : write_base64(const char* const str, const size_t buf_size, char* const buf)
     191                 :            : {
     192                 :          7 :   size_t i = 0;
     193                 :          7 :   size_t o = 0;
     194                 :            : 
     195         [ +  + ]:         41 :   for (; str[i]; ++i) {
     196         [ +  + ]:         35 :     if (is_base64(str[i])) {
     197                 :         22 :       o += write_char(str[i], buf_size, buf, o);
     198         [ +  + ]:         13 :     } else if (!is_space(str[i])) {
     199                 :          1 :       break;
     200                 :            :     }
     201                 :            :   }
     202                 :            : 
     203   [ +  +  +  + ]:          7 :   if (o == 0 || o % 4 != 0) {
     204                 :          5 :     return VRESULT(EXESS_EXPECTED_BASE64, i, o);
     205                 :            :   }
     206                 :            : 
     207                 :          2 :   return VRESULT(EXESS_SUCCESS, i, o);
     208                 :            : }
     209                 :            : 
     210                 :            : static ExessVariableResult
     211                 :         42 : write_bounded(const char* const   str,
     212                 :            :               const ExessDatatype datatype,
     213                 :            :               const size_t        buf_size,
     214                 :            :               char* const         buf)
     215                 :            : {
     216                 :         42 :   EXESS_ALIGN uint8_t value[EXESS_MAX_FIXED_SIZE] = {0};
     217                 :            : 
     218                 :            :   const ExessVariableResult vr =
     219                 :         42 :     exess_read_value(datatype, str, sizeof(value), value);
     220                 :            : 
     221         [ +  + ]:         42 :   if (vr.status) {
     222                 :          4 :     return VRESULT(vr.status, vr.read_count, 0U);
     223                 :            :   }
     224                 :            : 
     225                 :            :   const ExessResult w =
     226                 :         38 :     exess_write_value(datatype, vr.read_count, value, buf_size, buf);
     227                 :         38 :   return VRESULT(w.status, vr.read_count, w.count);
     228                 :            : }
     229                 :            : 
     230                 :            : EXESS_NONBLOCKING ExessVariableResult
     231                 :        204 : exess_write_canonical(const ExessDatatype datatype,
     232                 :            :                       const char* const   str,
     233                 :            :                       const size_t        buf_size,
     234                 :            :                       char* const         buf)
     235                 :            : {
     236                 :         71 :   const ExessVariableResult r =
     237                 :         54 :     (datatype == EXESS_DECIMAL) ? write_decimal(str, buf_size, buf)
     238   [ +  +  +  + ]:        329 :     : ((datatype == EXESS_INTEGER) ||
     239         [ +  + ]:        113 :        (datatype == EXESS_NON_POSITIVE_INTEGER) ||
     240         [ +  + ]:        103 :        (datatype == EXESS_NEGATIVE_INTEGER) ||
     241         [ +  + ]:         90 :        (datatype == EXESS_NON_NEGATIVE_INTEGER) ||
     242                 :            :        (datatype == EXESS_POSITIVE_INTEGER))
     243                 :         71 :       ? write_integer(datatype, str, buf_size, buf)
     244         [ +  + ]:        171 :     : (datatype == EXESS_DATE_TIME) ? write_date_time(str, buf_size, buf)
     245         [ +  + ]:         88 :     : (datatype == EXESS_HEX)       ? write_hex(str, buf_size, buf)
     246         [ +  + ]:         65 :     : (datatype == EXESS_BASE64) ? write_base64(str, buf_size, buf)
     247         [ +  + ]:         49 :                                  : write_bounded(str, datatype, buf_size, buf);
     248                 :            : 
     249                 :        204 :   const ExessResult w = end_write(r.status, buf_size, buf, r.write_count);
     250                 :        204 :   return VRESULT(w.status, r.read_count, w.count);
     251                 :            : }

Generated by: LCOV version 1.16