Initial commit
This commit is contained in:
@@ -0,0 +1,446 @@
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <memory.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "malunal/version.h"
|
||||
|
||||
|
||||
static
|
||||
malunal_cstr_t
|
||||
describe(malunal_int32_t code) {
|
||||
switch (code) {
|
||||
case VERSION_ERROR_NULL_INPUT:
|
||||
return "Version input or output pointer was null.";
|
||||
case VERSION_ERROR_INVALID_NUMBER:
|
||||
return "Expected a numeric version component but found none, or trailing "
|
||||
"characters remained after it.";
|
||||
case VERSION_ERROR_UNEXPECTED_CHAR:
|
||||
return "An unexpected character was encountered while parsing the version.";
|
||||
case VERSION_ERROR_MISSING_PART:
|
||||
return "The version string was missing one of its required parts.";
|
||||
case VERSION_ERROR_LEADING_ZERO:
|
||||
return "A numeric version component had a disallowed leading zero.";
|
||||
case VERSION_ERROR_VALUE_OVERFLOW:
|
||||
return "A numeric version component was too large to represent.";
|
||||
}
|
||||
|
||||
return "Unknown version error";
|
||||
}
|
||||
|
||||
const error_domain_t ERROR_DOMAIN_VERSION_T = {
|
||||
.describe = &describe,
|
||||
.name = "malunal.version.error"
|
||||
};
|
||||
|
||||
|
||||
static
|
||||
error_t
|
||||
parse_core_number(
|
||||
strview_mptr_t verstr,
|
||||
malunal_size_t* outval
|
||||
) {
|
||||
if (!isdigit((unsigned char)*verstr->beg))
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_INVALID_NUMBER
|
||||
};
|
||||
|
||||
malunal_cstr_t start = verstr->beg;
|
||||
while (isdigit((unsigned char)*verstr->beg))
|
||||
verstr->beg++;
|
||||
|
||||
if (*verstr->beg != '.')
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = !(
|
||||
strview_length(verstr) == 0 ||
|
||||
*verstr->beg == '-' ||
|
||||
*verstr->beg == '+' ||
|
||||
*verstr->beg == '.'
|
||||
) ? VERSION_ERROR_UNEXPECTED_CHAR
|
||||
: VERSION_ERROR_MISSING_PART
|
||||
};
|
||||
|
||||
if (verstr->beg - start > 1 && start[0] == '0')
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_LEADING_ZERO
|
||||
};
|
||||
|
||||
strview_t walked = { start, verstr->beg };
|
||||
malunal_int64_t value = 0;
|
||||
error_t result = strview_to_int64(&walked, &value);
|
||||
if (result.domain == &ERROR_DOMAIN_STRVIEW_T) {
|
||||
if (result.code == STRVIEW_ERROR_OUT_OF_RANGE)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_VALUE_OVERFLOW
|
||||
};
|
||||
|
||||
if (result.code == STRVIEW_ERROR_UNCONSUMED)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_INVALID_NUMBER
|
||||
};
|
||||
}
|
||||
|
||||
*outval = (malunal_size_t)value;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static
|
||||
error_t
|
||||
parse_patch_number(
|
||||
strview_mptr_t verstr,
|
||||
malunal_size_t* outval
|
||||
) {
|
||||
if (!isdigit((unsigned char)*verstr->beg))
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_INVALID_NUMBER
|
||||
};
|
||||
|
||||
malunal_cstr_t start = verstr->beg;
|
||||
while (isdigit((unsigned char)*verstr->beg))
|
||||
verstr->beg++;
|
||||
|
||||
if (
|
||||
strview_length(verstr) != 0 &&
|
||||
*verstr->beg != '-' &&
|
||||
*verstr->beg != '+'
|
||||
) return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_UNEXPECTED_CHAR
|
||||
};
|
||||
|
||||
if (verstr->beg - start > 1 && start[0] == '0')
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_LEADING_ZERO
|
||||
};
|
||||
|
||||
strview_t walked = { start, verstr->beg };
|
||||
malunal_int64_t value = 0;
|
||||
error_t result = strview_to_int64(&walked, &value);
|
||||
if (result.domain == &ERROR_DOMAIN_STRVIEW_T) {
|
||||
if (result.code == STRVIEW_ERROR_OUT_OF_RANGE)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_VALUE_OVERFLOW
|
||||
};
|
||||
|
||||
if (result.code == STRVIEW_ERROR_UNCONSUMED)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_INVALID_NUMBER
|
||||
};
|
||||
}
|
||||
|
||||
*outval = (malunal_size_t)value;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static
|
||||
error_t
|
||||
parse_preview_string(
|
||||
strview_mptr_t verstr,
|
||||
strview_mptr_t outstr
|
||||
) {
|
||||
if (*verstr->beg != '-')
|
||||
return NO_ERROR;
|
||||
|
||||
malunal_cstr_t start = ++verstr->beg;
|
||||
if (strview_length(verstr) == 0 || *verstr->beg == '+')
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_MISSING_PART
|
||||
};
|
||||
|
||||
while (strview_length(verstr) && *verstr->beg != '+')
|
||||
verstr->beg++;
|
||||
|
||||
*outstr = (strview_t) {
|
||||
.beg = start,
|
||||
.end = verstr->beg
|
||||
};
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static
|
||||
error_t
|
||||
parse_build_string(
|
||||
strview_mptr_t verstr,
|
||||
strview_mptr_t outstr
|
||||
) {
|
||||
if (*verstr->beg != '+')
|
||||
return NO_ERROR;
|
||||
|
||||
malunal_cstr_t start = ++verstr->beg;
|
||||
if (strview_length(verstr) == 0)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_MISSING_PART
|
||||
};
|
||||
|
||||
while (strview_length(verstr) != 0)
|
||||
verstr->beg++;
|
||||
|
||||
*outstr = (strview_t) {
|
||||
.beg = start,
|
||||
.end = verstr->beg
|
||||
};
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
error_t
|
||||
parse_version(
|
||||
strview_iptr_t verstr,
|
||||
version_mptr_t out
|
||||
) {
|
||||
if (verstr == null)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_NULL_INPUT
|
||||
};
|
||||
|
||||
if (out == null)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_NULL_INPUT
|
||||
};
|
||||
|
||||
memset(out, 0, sizeof(version_t));
|
||||
if (verstr->beg == null)
|
||||
return NO_ERROR;
|
||||
|
||||
error_t result;
|
||||
strview_t tempstr = {
|
||||
.beg = verstr->beg,
|
||||
.end = verstr->end
|
||||
};
|
||||
|
||||
result = parse_core_number(&tempstr, &out->major);
|
||||
if (result.domain != null)
|
||||
return result;
|
||||
|
||||
tempstr.beg++;
|
||||
result = parse_core_number(&tempstr, &out->minor);
|
||||
if (result.domain != null)
|
||||
return result;
|
||||
|
||||
tempstr.beg++;
|
||||
result = parse_patch_number(&tempstr, &out->patch);
|
||||
if (result.domain != null)
|
||||
return result;
|
||||
|
||||
result = parse_preview_string(&tempstr, &out->preview);
|
||||
if (result.domain != null)
|
||||
return result;
|
||||
|
||||
result = parse_build_string(&tempstr, &out->build);
|
||||
if (result.domain != null)
|
||||
return result;
|
||||
|
||||
if (strview_length(&tempstr) != 0)
|
||||
return (error_t) {
|
||||
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||
.code = VERSION_ERROR_UNEXPECTED_CHAR
|
||||
};
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static
|
||||
malunal_bool_t
|
||||
identifier_is_numeric(strview_iptr_t id) {
|
||||
if (strview_length(id) == 0)
|
||||
return false;
|
||||
|
||||
malunal_cstr_t p;
|
||||
for (p = id->beg; p < id->end; p++)
|
||||
if (!isdigit(*p))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
malunal_int32_t
|
||||
compare_numeric_identifier(
|
||||
strview_iptr_t a,
|
||||
strview_iptr_t b
|
||||
) {
|
||||
malunal_size_t alen = strview_length(a);
|
||||
malunal_size_t blen = strview_length(b);
|
||||
malunal_cstr_t ap = a->beg;
|
||||
malunal_cstr_t bp = b->beg;
|
||||
|
||||
// Ignore leading zeros so two differently-padded numbers compare fairly.
|
||||
while (alen > 1 && *ap == '0') { ap++; alen--; }
|
||||
while (blen > 1 && *bp == '0') { bp++; blen--; }
|
||||
|
||||
if (alen != blen) return alen < blen ? -1 : 1;
|
||||
if (alen == 0) return 0;
|
||||
|
||||
malunal_int32_t cmp = strncmp(ap, bp, alen);
|
||||
return cmp < 0 ? -1 : (cmp > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
static
|
||||
malunal_int32_t
|
||||
compare_alphanumeric_identifier(
|
||||
strview_iptr_t a,
|
||||
strview_iptr_t b
|
||||
) {
|
||||
malunal_size_t alen = strview_length(a);
|
||||
malunal_size_t blen = strview_length(b);
|
||||
malunal_size_t minlen = alen < blen ? alen : blen;
|
||||
malunal_int32_t cmp = minlen > 0
|
||||
? strncmp(a->beg, b->beg, (size_t)minlen)
|
||||
: 0;
|
||||
|
||||
if (cmp != 0)
|
||||
return cmp < 0 ? -1 : 1;
|
||||
|
||||
return alen != blen
|
||||
? alen < blen ? -1 : 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
static
|
||||
malunal_int32_t
|
||||
compare_preview_identifier(
|
||||
strview_iptr_t a,
|
||||
strview_iptr_t b
|
||||
) {
|
||||
malunal_bool_t anum = identifier_is_numeric(a);
|
||||
malunal_bool_t bnum = identifier_is_numeric(b);
|
||||
if (anum && bnum)
|
||||
return compare_numeric_identifier(a, b);
|
||||
|
||||
// Per semver, numeric identifiers always have lower precedence than
|
||||
// alphanumeric ones.
|
||||
return anum == bnum
|
||||
? compare_alphanumeric_identifier(a, b)
|
||||
: anum ? -1 : 1;
|
||||
}
|
||||
|
||||
static
|
||||
malunal_int32_t
|
||||
compare_preview(
|
||||
strview_iptr_t self,
|
||||
strview_iptr_t other
|
||||
) {
|
||||
strview_t sremain = *self;
|
||||
strview_t oremain = *other;
|
||||
|
||||
for (;;) {
|
||||
malunal_bool_t sdone = strview_length(&sremain) == 0;
|
||||
malunal_bool_t odone = strview_length(&oremain) == 0;
|
||||
if (sdone && odone)
|
||||
return 0;
|
||||
|
||||
// A pre-release with fewer identifiers has lower precedence, as long as
|
||||
// all the preceding identifiers matched.
|
||||
if (sdone) return -1;
|
||||
if (odone) return 1;
|
||||
|
||||
malunal_cstr_t send = sremain.beg;
|
||||
while (send < sremain.end && *send != '.')
|
||||
send++;
|
||||
strview_t sid = { sremain.beg, send };
|
||||
|
||||
malunal_cstr_t oend = oremain.beg;
|
||||
while (oend < oremain.end && *oend != '.')
|
||||
oend++;
|
||||
strview_t oid = { oremain.beg, oend };
|
||||
|
||||
malunal_int32_t cmp = compare_preview_identifier(&sid, &oid);
|
||||
if (cmp != 0)
|
||||
return cmp;
|
||||
|
||||
sremain.beg = send < sremain.end ? send + 1 : send;
|
||||
oremain.beg = oend < oremain.end ? oend + 1 : oend;
|
||||
}
|
||||
}
|
||||
|
||||
malunal_int32_t
|
||||
version_compare(
|
||||
version_iptr_t self,
|
||||
version_iptr_t other
|
||||
) {
|
||||
if (self == other) return 0;
|
||||
if (self == null) return -1;
|
||||
if (other == null) return 1;
|
||||
|
||||
if (self->major != other->major) return self->major < other->major ? -1 : 1;
|
||||
if (self->minor != other->minor) return self->minor < other->minor ? -1 : 1;
|
||||
if (self->patch != other->patch) return self->patch < other->patch ? -1 : 1;
|
||||
|
||||
malunal_bool_t self_has_preview = strview_length(&self->preview) != 0;
|
||||
malunal_bool_t other_has_preview = strview_length(&other->preview) != 0;
|
||||
if (self_has_preview != other_has_preview)
|
||||
return self_has_preview ? -1 : 1;
|
||||
|
||||
return self_has_preview
|
||||
? compare_preview(&self->preview, &other->preview)
|
||||
: 0;
|
||||
}
|
||||
|
||||
malunal_int32_t
|
||||
version_string(
|
||||
version_iptr_t self,
|
||||
malunal_str_t buffer,
|
||||
malunal_size_t length
|
||||
) {
|
||||
if (self == null || buffer == null || length == 0)
|
||||
return -1;
|
||||
|
||||
malunal_size_t written = snprintf(
|
||||
buffer,
|
||||
length,
|
||||
"%llu.%llu.%llu",
|
||||
self->major,
|
||||
self->minor,
|
||||
self->patch
|
||||
);
|
||||
|
||||
if (written < 0 || written >= length)
|
||||
return -1;
|
||||
|
||||
malunal_size_t preview_len =
|
||||
strview_length(&self->preview);
|
||||
if (preview_len > 0) {
|
||||
malunal_int32_t n = snprintf(
|
||||
buffer + written,
|
||||
length - written,
|
||||
"-%.*s",
|
||||
preview_len,
|
||||
self->preview.beg
|
||||
);
|
||||
|
||||
if (n < 0 || (written + n) >= length)
|
||||
return -1;
|
||||
written += n;
|
||||
}
|
||||
|
||||
malunal_size_t build_len =
|
||||
strview_length(&self->build);
|
||||
if (build_len > 0) {
|
||||
malunal_int32_t n = snprintf(
|
||||
buffer + written,
|
||||
length - written,
|
||||
"+%.*s",
|
||||
build_len,
|
||||
self->build.beg
|
||||
);
|
||||
|
||||
if (n < 0 || (written + n) >= length)
|
||||
return -1;
|
||||
written += n;
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
Reference in New Issue
Block a user