1/*-
2* SPDX-License-Identifier: BSD-2-Clause
3* Copyright (c) 2022 Aymeric Wibo <obiwac@gmail.com>
4*/
5
6#include <ctype.h>
7#include <stddef.h>
8
9int
10strverscmp(const char *s1, const char *s2)
11{
12	size_t digit_count_1, digit_count_2;
13	size_t zeros_count_1, zeros_count_2;
14	const unsigned char *num_1, *num_2;
15	const unsigned char *u1 = __DECONST(const unsigned char *, s1);
16	const unsigned char *u2 = __DECONST(const unsigned char *, s2);
17
18	/*
19	 * If pointers are the same, no need to go through to process of
20	 * comparing them.
21	 */
22	if (s1 == s2)
23		return (0);
24
25	while (*u1 != '\0' && *u2 != '\0') {
26		/* If either character is not a digit, act like strcmp(3). */
27
28		if (!isdigit(*u1) || !isdigit(*u2)) {
29			if (*u1 != *u2)
30				return (*u1 - *u2);
31			u1++;
32			u2++;
33			continue;
34		}
35		if (*u1 == '0' || *u2 == '0') {
36			/*
37			 * Treat leading zeros as if they were the fractional
38			 * part of a number, i.e. as if they had a decimal point
39			 * in front. First, count the leading zeros (more zeros
40			 * == smaller number).
41			 */
42			zeros_count_1 = 0;
43			zeros_count_2 = 0;
44			for (; *u1 == '0'; u1++)
45				zeros_count_1++;
46			for (; *u2 == '0'; u2++)
47				zeros_count_2++;
48			if (zeros_count_1 != zeros_count_2)
49				return (zeros_count_2 - zeros_count_1);
50
51			/* Handle the case where 0 < 09. */
52			if (!isdigit(*u1) && isdigit(*u2))
53				return (1);
54			if (!isdigit(*u2) && isdigit(*u1))
55				return (-1);
56		} else {
57			/*
58			 * No leading zeros; we're simply comparing two numbers.
59			 * It is necessary to first count how many digits there
60			 * are before going back to compare each digit, so that
61			 * e.g. 7 is not considered larger than 60.
62			 */
63			num_1 = u1;
64			num_2 = u2;
65
66			/* Count digits (more digits == larger number). */
67			for (; isdigit(*u1); u1++)
68				;
69			for (; isdigit(*u2); u2++)
70				;
71			digit_count_1 = u1 - num_1;
72			digit_count_2 = u2 - num_2;
73			if (digit_count_1 != digit_count_2)
74				return (digit_count_1 - digit_count_2);
75
76			/*
77			 * If there are the same number of digits, go back to
78			 * the start of the number.
79			 */
80			u1 = num_1;
81			u2 = num_2;
82		}
83
84		/* Compare each digit until there are none left. */
85		for (; isdigit(*u1) && isdigit(*u2); u1++, u2++) {
86			if (*u1 != *u2)
87				return (*u1 - *u2);
88		}
89	}
90	return (*u1 - *u2);
91}
92