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 "number_utils.h"
6 : : #include "read_utils.h"
7 : : #include "result.h"
8 : : #include "time_utils.h"
9 : : #include "write_utils.h"
10 : :
11 : : #include <exess/exess.h>
12 : :
13 : : #include <stdbool.h>
14 : : #include <stdint.h>
15 : : #include <stdlib.h>
16 : : #include <string.h>
17 : :
18 : : typedef enum { NONE, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND } Field;
19 : :
20 : : static EXESS_NONBLOCKING ExessStatus
21 : 6270 : set_field(ExessDuration* const out,
22 : : const Field last_field,
23 : : const Field field,
24 : : const uint64_t value)
25 : : {
26 [ + + ]: 6270 : if (value >= INT32_MAX) {
27 : 7 : return EXESS_OUT_OF_RANGE;
28 : : }
29 : :
30 [ + + ]: 6263 : if (field <= last_field) {
31 : 6 : return EXESS_BAD_ORDER;
32 : : }
33 : :
34 : 6257 : EXESS_CONSTEXPR uint64_t factors[7] = {
35 : : 0UL, 12UL, 1UL, 24UL * 60UL * 60UL, 60UL * 60UL, 60UL, 1UL};
36 : :
37 : 6257 : const uint64_t factor = factors[field];
38 [ + + ]: 6257 : int32_t* const target = (field < DAY) ? &out->months : &out->seconds;
39 : :
40 : 6257 : const int64_t new_value = (int64_t)*target + (int64_t)(factor * value);
41 [ + + ]: 6257 : if (new_value >= INT32_MAX) {
42 : 4 : return EXESS_OUT_OF_RANGE;
43 : : }
44 : :
45 : 6253 : *target = (int32_t)new_value;
46 : 6253 : return EXESS_SUCCESS;
47 : : }
48 : :
49 : : static EXESS_NONBLOCKING ExessResult
50 : 1178 : read_duration_date(ExessDuration* const out, const char* const str)
51 : : {
52 : 1178 : size_t i = 0U;
53 : 1178 : ExessStatus st = EXESS_SUCCESS;
54 : 1178 : unsigned last_field = 0U;
55 : :
56 [ + + + + : 4336 : while (!st && last_field < DAY && str[i] != 'T') {
+ + ]
57 : : // Read the unsigned integer value
58 : 3237 : uint64_t value = 0U;
59 : 3237 : const ExessResult r = read_digits(&value, str + i);
60 : 3237 : i += r.count;
61 [ + + ]: 3237 : if (r.status) {
62 [ + + ]: 74 : st = r.count ? r.status : EXESS_SUCCESS;
63 : 74 : break;
64 : : }
65 : :
66 : : // Read Y, M, or D field tag
67 : 6326 : const Field field = (str[i] == 'Y') ? YEAR
68 [ + + + + ]: 4254 : : (str[i] == 'M') ? MONTH
69 [ + + ]: 1091 : : (str[i] == 'D') ? DAY
70 : : : NONE;
71 [ + + ]: 3163 : if (!field) {
72 : 5 : return RESULT(EXESS_EXPECTED_DATE_TAG, i);
73 : : }
74 : :
75 : : // Set the field value and advance
76 : 3158 : st = set_field(out, (Field)last_field, field, value);
77 : 3158 : last_field = field;
78 : 3158 : ++i;
79 : : }
80 : :
81 [ + + ]: 1173 : return i > 0 ? RESULT(st, i) : RESULT(EXESS_EXPECTED_DIGIT, i);
82 : : }
83 : :
84 : : static ExessResult
85 : 1113 : read_duration_time(ExessDuration* const out, const char* const str)
86 : : {
87 : 1113 : size_t i = 0U;
88 : 1113 : ExessStatus st = EXESS_SUCCESS;
89 : 1113 : unsigned last_field = 0U;
90 : :
91 [ + + + + ]: 4225 : while (!st && last_field < SECOND) {
92 : : // Read the unsigned integer value
93 : 3149 : uint64_t value = 0U;
94 : 3149 : const ExessResult r = read_digits(&value, str + i);
95 : 3149 : i += r.count;
96 [ + + ]: 3149 : if (r.status) {
97 [ + + ]: 31 : st = r.count ? r.status : EXESS_SUCCESS;
98 : 31 : break;
99 : : }
100 : :
101 : 3118 : Field field = YEAR;
102 [ + + ]: 3118 : if (str[i] == '.') {
103 : 1049 : uint32_t nanoseconds = 0U;
104 [ + + ]: 1049 : if (!is_digit(str[++i])) {
105 : 1 : return RESULT(EXESS_EXPECTED_DIGIT, i);
106 : : }
107 : :
108 : 1048 : const ExessResult s = read_nanoseconds(&nanoseconds, str + i);
109 : 1048 : i += s.count;
110 [ + + ]: 1048 : if (str[i] != 'S') {
111 : 4 : return RESULT(EXESS_EXPECTED_SECOND_TAG, i);
112 : : }
113 : :
114 : 1044 : field = SECOND;
115 : 1044 : out->nanoseconds = (int32_t)nanoseconds;
116 [ + + ]: 2069 : } else if (str[i] == 'H') {
117 : 1013 : field = HOUR;
118 [ + + ]: 1056 : } else if (str[i] == 'M') {
119 : 1030 : field = MINUTE;
120 [ + + ]: 26 : } else if (str[i] == 'S') {
121 : 25 : field = SECOND;
122 : : } else {
123 : 1 : return RESULT(EXESS_EXPECTED_TIME_TAG, i);
124 : : }
125 : :
126 : : // Set the field value and advance
127 : 3112 : st = set_field(out, (Field)last_field, field, value);
128 : 3112 : last_field = (unsigned)field;
129 : 3112 : ++i;
130 : : }
131 : :
132 [ + + ]: 1107 : return i > 0 ? RESULT(st, i) : RESULT(EXESS_EXPECTED_DIGIT, i);
133 : : }
134 : :
135 : : static EXESS_NONBLOCKING ExessOrder
136 : 50 : compare_duration_partial(const ExessDuration lhs, const ExessDuration rhs)
137 : : {
138 : : // See https://www.w3.org/TR/xmlschema11-2/#duration
139 : :
140 : : const ExessDateTime a = {1696, 9, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
141 : : const ExessDateTime b = {1697, 2, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
142 : : const ExessDateTime c = {1903, 3, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
143 : : const ExessDateTime d = {1903, 7, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
144 : :
145 : 50 : const ExessOrder cmp = exess_compare_date_time(
146 : : exess_add_date_time_duration(a, lhs), exess_add_date_time_duration(a, rhs));
147 : :
148 [ + + ]: 50 : if (cmp == exess_compare_date_time(exess_add_date_time_duration(b, lhs),
149 : 40 : exess_add_date_time_duration(b, rhs)) &&
150 [ + + ]: 40 : cmp == exess_compare_date_time(exess_add_date_time_duration(c, lhs),
151 : 28 : exess_add_date_time_duration(c, rhs)) &&
152 [ + + ]: 28 : cmp == exess_compare_date_time(exess_add_date_time_duration(d, lhs),
153 : : exess_add_date_time_duration(d, rhs))) {
154 : 26 : return cmp;
155 : : }
156 : :
157 : 24 : return (lhs.months < rhs.months) ? EXESS_ORDER_MAYBE_LESS
158 [ + + + + ]: 32 : : (rhs.months < lhs.months) ? EXESS_ORDER_MAYBE_GREATER
159 [ + + ]: 8 : : (lhs.seconds < rhs.seconds) ? EXESS_ORDER_MAYBE_LESS
160 : : : EXESS_ORDER_MAYBE_GREATER;
161 : : }
162 : :
163 : : EXESS_NONBLOCKING ExessOrder
164 : 104 : exess_compare_duration(const ExessDuration lhs, const ExessDuration rhs)
165 : : {
166 : : // Exactly equal
167 [ + + + + ]: 104 : if (lhs.months == rhs.months && lhs.seconds == rhs.seconds &&
168 [ + + ]: 26 : lhs.nanoseconds == rhs.nanoseconds) {
169 : 18 : return EXESS_ORDER_EQUAL;
170 : : }
171 : :
172 : : // Totally ordered yearMonthDuration
173 [ + + + + : 86 : if (!lhs.seconds && !lhs.nanoseconds && !rhs.seconds && !rhs.nanoseconds) {
+ + + + ]
174 : 12 : return (lhs.months < rhs.months) ? EXESS_ORDER_STRICTLY_LESS
175 [ + + ]: 12 : : EXESS_ORDER_STRICTLY_GREATER;
176 : : }
177 : :
178 : : // Totally ordered dayTimeDuration
179 [ + + + + ]: 74 : if (!lhs.months && !rhs.months) {
180 : 24 : return (lhs.seconds < rhs.seconds) ? EXESS_ORDER_STRICTLY_LESS
181 [ + + + + ]: 28 : : (rhs.seconds < lhs.seconds) ? EXESS_ORDER_STRICTLY_GREATER
182 [ + + ]: 4 : : (lhs.nanoseconds < rhs.nanoseconds) ? EXESS_ORDER_STRICTLY_LESS
183 : : : EXESS_ORDER_STRICTLY_GREATER;
184 : : }
185 : :
186 : 50 : return compare_duration_partial(lhs, rhs);
187 : : }
188 : :
189 : : EXESS_NONBLOCKING ExessResult
190 : 1261 : exess_read_duration(const char* const str, ExessDuration* const out)
191 : : {
192 : 1261 : memset(out, 0, sizeof(*out));
193 : :
194 : 1261 : size_t i = skip_whitespace(str);
195 : 1261 : bool is_negative = false;
196 [ + + ]: 1261 : if (str[i] == '-') {
197 : 514 : is_negative = true;
198 : 514 : ++i;
199 : : }
200 : :
201 [ + + ]: 1261 : if (str[i] != 'P') {
202 : 7 : return RESULT(EXESS_EXPECTED_DURATION, i);
203 : : }
204 : :
205 : 1254 : ++i;
206 [ + + ]: 1254 : if (str[i] != 'T') {
207 : 1178 : ExessResult r = read_duration_date(out, str + i);
208 [ + + ]: 1178 : if (r.status) {
209 : 20 : return RESULT(r.status, i + r.count);
210 : : }
211 : :
212 : 1158 : i += r.count;
213 : : }
214 : :
215 [ + + ]: 1234 : if (str[i] == 'T') {
216 : 1113 : ++i;
217 : :
218 : 1113 : ExessResult r = read_duration_time(out, str + i);
219 [ + + ]: 1113 : if (r.status) {
220 : 19 : return RESULT(r.status, i + r.count);
221 : : }
222 : :
223 : 1094 : i += r.count;
224 : : }
225 : :
226 [ + + ]: 1215 : if (is_negative) {
227 : 514 : out->months = -out->months;
228 : 514 : out->seconds = -out->seconds;
229 : 514 : out->nanoseconds = -out->nanoseconds;
230 : : }
231 : :
232 : 1215 : return RESULT(EXESS_SUCCESS, i);
233 : : }
234 : :
235 : : static size_t
236 : 10548 : write_int_field(const uint32_t value,
237 : : const char tag,
238 : : const size_t buf_size,
239 : : char* const buf,
240 : : const size_t i)
241 : : {
242 [ + + ]: 10548 : if (!value) {
243 : 385 : return 0;
244 : : }
245 : :
246 : 10163 : const ExessResult r = write_digits(value, buf_size, buf, i);
247 : 10163 : return r.count + write_char(tag, buf_size, buf, i + r.count);
248 : : }
249 : :
250 : : static size_t
251 : 2087 : write_seconds(const uint8_t abs_seconds,
252 : : const uint32_t abs_nanoseconds,
253 : : const size_t buf_size,
254 : : char* const buf,
255 : : const size_t i)
256 : : {
257 : 2087 : const ExessResult r = write_digits(abs_seconds, buf_size, buf, i);
258 : :
259 : 2087 : size_t len = r.count;
260 [ + + ]: 2087 : if (abs_nanoseconds) {
261 : 2067 : len += write_nanoseconds(abs_nanoseconds, buf_size, buf, i + len);
262 : : }
263 : :
264 : 2087 : return len + write_char('S', buf_size, buf, i + len);
265 : : }
266 : :
267 : : EXESS_NONBLOCKING ExessResult
268 : 2131 : exess_write_duration(const ExessDuration value,
269 : : const size_t buf_size,
270 : : char* const buf)
271 : : {
272 : : // Write zero as a special case
273 : 2131 : size_t i = 0;
274 [ + + + + : 2131 : if (value.months == 0 && value.seconds == 0 && value.nanoseconds == 0) {
+ + ]
275 : 4 : i += write_string(3, "P0Y", buf_size, buf, i);
276 : 4 : return end_write(EXESS_SUCCESS, buf_size, buf, i);
277 : : }
278 : :
279 [ + + + + ]: 2127 : if (value.months == INT32_MIN || value.seconds == INT32_MIN) {
280 : 3 : return end_write(EXESS_OUT_OF_RANGE, buf_size, buf, 0);
281 : : }
282 : :
283 : 2124 : const bool is_negative =
284 [ + + + + : 2124 : (value.months < 0 || value.seconds < 0 || value.nanoseconds < 0);
+ + ]
285 : :
286 [ + + ]: 2124 : if (is_negative &&
287 [ + + + + : 1019 : (value.months > 0 || value.seconds > 0 || value.nanoseconds > 0)) {
+ + ]
288 : 6 : return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
289 : : }
290 : :
291 : : // Write duration prefix
292 [ + + ]: 2118 : i += is_negative ? write_char('-', buf_size, buf, i) : 0U;
293 : 2118 : i += write_char('P', buf_size, buf, i);
294 : :
295 : 2118 : const uint32_t abs_years = (uint32_t)(abs(value.months) / 12);
296 : 2118 : const uint32_t abs_months = (uint32_t)(abs(value.months) % 12);
297 : 2118 : const uint32_t abs_days = (uint32_t)(abs(value.seconds) / (24 * 60 * 60));
298 : 2118 : const uint32_t abs_hours = (uint32_t)(abs(value.seconds) / 60 / 60 % 24);
299 : 2118 : const uint32_t abs_minutes = (uint32_t)(abs(value.seconds) / 60 % 60);
300 : 2118 : const uint8_t abs_seconds = (uint8_t)(abs(value.seconds) % 60);
301 : 2118 : const uint32_t abs_nanoseconds = (uint32_t)abs(value.nanoseconds);
302 : :
303 : : // Write date segments if present
304 : 2118 : i += write_int_field(abs_years, 'Y', buf_size, buf, i);
305 : 2118 : i += write_int_field(abs_months, 'M', buf_size, buf, i);
306 : 2118 : i += write_int_field(abs_days, 'D', buf_size, buf, i);
307 : :
308 : : // Write time segments if present
309 : 2118 : const bool has_time = abs_hours + abs_minutes + abs_seconds + abs_nanoseconds;
310 [ + + ]: 2118 : if (has_time) {
311 : 2097 : i += write_char('T', buf_size, buf, i);
312 : 2097 : i += write_int_field(abs_hours, 'H', buf_size, buf, i);
313 : 2097 : i += write_int_field(abs_minutes, 'M', buf_size, buf, i);
314 [ + + ]: 2097 : if (abs_seconds + abs_nanoseconds) {
315 : 2087 : i += write_seconds(abs_seconds, abs_nanoseconds, buf_size, buf, i);
316 : : }
317 : : }
318 : :
319 : 2118 : return end_write(EXESS_SUCCESS, buf_size, buf, i);
320 : : }
|