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 : : }
|