293 lines
10 KiB
C
293 lines
10 KiB
C
|
|
/*
|
||
|
|
* The strto* family -- TODO.md section 3.1.
|
||
|
|
*
|
||
|
|
* These are the real implementation behind the ato* wrappers and the thing
|
||
|
|
* akbasic had to hand-write for itself (its src/convert.c, ~60 lines) because
|
||
|
|
* the library would not do it. What they add over the ato* forms is a base and
|
||
|
|
* an endptr: pass a non-NULL endptr to parse a number off the front of a longer
|
||
|
|
* string and find out where it stopped, and the trailing-junk check goes away
|
||
|
|
* because the caller has taken responsibility for what follows.
|
||
|
|
*
|
||
|
|
* tests/test_convert.c covers what the two families share; this file is the part
|
||
|
|
* that is specific to having a base and an endptr.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "aksl_capture.h"
|
||
|
|
|
||
|
|
#include <errno.h>
|
||
|
|
#include <limits.h>
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Base handling */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_base_is_honoured(void)
|
||
|
|
{
|
||
|
|
long out = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("ff", NULL, 16, &out));
|
||
|
|
AKSL_CHECK(out == 255);
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("1010", NULL, 2, &out));
|
||
|
|
AKSL_CHECK(out == 10);
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("777", NULL, 8, &out));
|
||
|
|
AKSL_CHECK(out == 511);
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("z", NULL, 36, &out));
|
||
|
|
AKSL_CHECK(out == 35);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Base 0 auto-detects the 0x and 0 prefixes, which is the counterpart to
|
||
|
|
* tests/test_convert.c's test that aksl_atoi refuses "0x10". */
|
||
|
|
static int test_base_zero_detects_the_prefix(void)
|
||
|
|
{
|
||
|
|
long out = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("0x10", NULL, 0, &out));
|
||
|
|
AKSL_CHECK(out == 16);
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("010", NULL, 0, &out));
|
||
|
|
AKSL_CHECK(out == 8);
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("10", NULL, 0, &out));
|
||
|
|
AKSL_CHECK(out == 10);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* A digit that does not belong to the base is where the number ends. */
|
||
|
|
static int test_digits_outside_the_base_end_the_number(void)
|
||
|
|
{
|
||
|
|
long out = 0;
|
||
|
|
char *end = NULL;
|
||
|
|
|
||
|
|
/* "9" is not an octal digit, so with a NULL endptr it is trailing junk. */
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtol("129", NULL, 8, &out), AKERR_VALUE);
|
||
|
|
/* With an endptr the caller gets the partial parse and the stopping point. */
|
||
|
|
AKSL_CHECK_OK(aksl_strtol("129", &end, 8, &out));
|
||
|
|
AKSL_CHECK(out == 10); /* 012 octal */
|
||
|
|
AKSL_CHECK(end != NULL && *end == '9');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* An invalid base is EINVAL from strtol(3) rather than a silent answer. */
|
||
|
|
static int test_invalid_base_is_rejected(void)
|
||
|
|
{
|
||
|
|
long out = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtol("10", NULL, 37, &out), EINVAL);
|
||
|
|
AKSL_CHECK(out == 0);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtol("10", NULL, 1, &out), EINVAL);
|
||
|
|
AKSL_CHECK(out == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* endptr */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* With an endptr, trailing text is the caller's business and not an error. This
|
||
|
|
* is the shape a tokenizer wants: parse a number, carry on from where it ended.
|
||
|
|
*/
|
||
|
|
static int test_endptr_reports_where_parsing_stopped(void)
|
||
|
|
{
|
||
|
|
const char *input = "42 rest of the line";
|
||
|
|
char *end = NULL;
|
||
|
|
long out = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strtol(input, &end, 10, &out));
|
||
|
|
AKSL_CHECK(out == 42);
|
||
|
|
AKSL_CHECK(end == input + 2);
|
||
|
|
AKSL_CHECK(strcmp(end, " rest of the line") == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Consuming nothing is still an error, endptr or not: there was no number. */
|
||
|
|
static int test_no_digits_is_an_error_even_with_an_endptr(void)
|
||
|
|
{
|
||
|
|
char *end = (char *)0x1;
|
||
|
|
long out = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strtol("nope", &end, 10, &out),
|
||
|
|
AKERR_VALUE, "no digits");
|
||
|
|
AKSL_CHECK(out == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Successive calls chained through endptr walk a whole list of numbers. */
|
||
|
|
static int test_endptr_chains_across_a_list(void)
|
||
|
|
{
|
||
|
|
const char *input = "1,2,3";
|
||
|
|
char *cursor = (char *)input;
|
||
|
|
long out = 0;
|
||
|
|
long total = 0;
|
||
|
|
int seen = 0;
|
||
|
|
|
||
|
|
while ( *cursor != '\0' ) {
|
||
|
|
AKSL_CHECK_OK(aksl_strtol(cursor, &cursor, 10, &out));
|
||
|
|
total += out;
|
||
|
|
seen += 1;
|
||
|
|
if ( *cursor == ',' ) {
|
||
|
|
cursor++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
AKSL_CHECK(seen == 3);
|
||
|
|
AKSL_CHECK(total == 6);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Ranges and signs */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_range_errors_per_type(void)
|
||
|
|
{
|
||
|
|
long lout = 99;
|
||
|
|
long long llout = 99;
|
||
|
|
unsigned long ulout = 99;
|
||
|
|
unsigned long long ullout = 99;
|
||
|
|
char buf[64];
|
||
|
|
|
||
|
|
snprintf(buf, sizeof(buf), "%lld9", (long long)LLONG_MAX);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtol(buf, NULL, 10, &lout), ERANGE);
|
||
|
|
AKSL_CHECK(lout == 0);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoll(buf, NULL, 10, &llout), ERANGE);
|
||
|
|
AKSL_CHECK(llout == 0);
|
||
|
|
|
||
|
|
snprintf(buf, sizeof(buf), "%llu9", (unsigned long long)ULLONG_MAX);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoul(buf, NULL, 10, &ulout), ERANGE);
|
||
|
|
AKSL_CHECK(ulout == 0);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoull(buf, NULL, 10, &ullout), ERANGE);
|
||
|
|
AKSL_CHECK(ullout == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* strtoul(3) and strtoull(3) accept a leading '-' and hand back the negation
|
||
|
|
* wrapped into the unsigned range, so "-1" parses as ULONG_MAX and reports
|
||
|
|
* nothing at all. That is exactly the silent failure this library exists to
|
||
|
|
* surface, so the sign is refused before the call.
|
||
|
|
*/
|
||
|
|
static int test_unsigned_forms_refuse_a_negative(void)
|
||
|
|
{
|
||
|
|
unsigned long ulout = 99;
|
||
|
|
unsigned long long ullout = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strtoul("-1", NULL, 10, &ulout),
|
||
|
|
AKERR_VALUE, "negative");
|
||
|
|
AKSL_CHECK(ulout == 0);
|
||
|
|
/* Including behind leading whitespace, which strtoul skips. */
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoul(" -1", NULL, 10, &ulout), AKERR_VALUE);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoull("-1", NULL, 10, &ullout), AKERR_VALUE);
|
||
|
|
AKSL_CHECK(ullout == 0);
|
||
|
|
|
||
|
|
/* A leading '+' is fine: it is a sign, not a negation. */
|
||
|
|
AKSL_CHECK_OK(aksl_strtoul("+7", NULL, 10, &ulout));
|
||
|
|
AKSL_CHECK(ulout == 7);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int test_unsigned_boundaries_round_trip(void)
|
||
|
|
{
|
||
|
|
unsigned long ulout = 0;
|
||
|
|
unsigned long long ullout = 0;
|
||
|
|
char buf[64];
|
||
|
|
|
||
|
|
snprintf(buf, sizeof(buf), "%lu", ULONG_MAX);
|
||
|
|
AKSL_CHECK_OK(aksl_strtoul(buf, NULL, 10, &ulout));
|
||
|
|
AKSL_CHECK(ulout == ULONG_MAX);
|
||
|
|
|
||
|
|
snprintf(buf, sizeof(buf), "%llu", ULLONG_MAX);
|
||
|
|
AKSL_CHECK_OK(aksl_strtoull(buf, NULL, 10, &ullout));
|
||
|
|
AKSL_CHECK(ullout == ULLONG_MAX);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Floating point */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_floating_point_forms(void)
|
||
|
|
{
|
||
|
|
double dout = 0.0;
|
||
|
|
float fout = 0.0f;
|
||
|
|
long double ldout = 0.0L;
|
||
|
|
char *end = NULL;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strtod("2.5", NULL, &dout));
|
||
|
|
AKSL_CHECK(dout == 2.5);
|
||
|
|
AKSL_CHECK_OK(aksl_strtof("2.5", NULL, &fout));
|
||
|
|
AKSL_CHECK(fout == 2.5f);
|
||
|
|
AKSL_CHECK_OK(aksl_strtold("2.5", NULL, &ldout));
|
||
|
|
AKSL_CHECK(ldout == 2.5L);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strtod("2.5rest", &end, &dout));
|
||
|
|
AKSL_CHECK(dout == 2.5);
|
||
|
|
AKSL_CHECK(strcmp(end, "rest") == 0);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtod("2.5rest", NULL, &dout), AKERR_VALUE);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtof("nope", NULL, &fout), AKERR_VALUE);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtold("nope", NULL, &ldout), AKERR_VALUE);
|
||
|
|
|
||
|
|
/* float has a much smaller range than double, and says so. */
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtof("1e300", NULL, &fout), ERANGE);
|
||
|
|
AKSL_CHECK(fout == 0.0f);
|
||
|
|
AKSL_CHECK_OK(aksl_strtod("1e300", NULL, &dout));
|
||
|
|
AKSL_CHECK(dout > 0.0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Argument validation */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_every_form_rejects_null_arguments(void)
|
||
|
|
{
|
||
|
|
long lout = 0;
|
||
|
|
long long llout = 0;
|
||
|
|
unsigned long ulout = 0;
|
||
|
|
unsigned long long ullout = 0;
|
||
|
|
double dout = 0.0;
|
||
|
|
float fout = 0.0f;
|
||
|
|
long double ldout = 0.0L;
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtol(NULL, NULL, 10, &lout), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtol("1", NULL, 10, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoll(NULL, NULL, 10, &llout), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoll("1", NULL, 10, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoul(NULL, NULL, 10, &ulout), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoul("1", NULL, 10, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoull(NULL, NULL, 10, &ullout), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtoull("1", NULL, 10, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtod(NULL, NULL, &dout), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtod("1", NULL, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtof(NULL, NULL, &fout), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtof("1", NULL, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtold(NULL, NULL, &ldout), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtold("1", NULL, NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
int failures = 0;
|
||
|
|
|
||
|
|
akerr_init();
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_base_is_honoured);
|
||
|
|
AKSL_RUN(failures, test_base_zero_detects_the_prefix);
|
||
|
|
AKSL_RUN(failures, test_digits_outside_the_base_end_the_number);
|
||
|
|
AKSL_RUN(failures, test_invalid_base_is_rejected);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_endptr_reports_where_parsing_stopped);
|
||
|
|
AKSL_RUN(failures, test_no_digits_is_an_error_even_with_an_endptr);
|
||
|
|
AKSL_RUN(failures, test_endptr_chains_across_a_list);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_range_errors_per_type);
|
||
|
|
AKSL_RUN(failures, test_unsigned_forms_refuse_a_negative);
|
||
|
|
AKSL_RUN(failures, test_unsigned_boundaries_round_trip);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_floating_point_forms);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_every_form_rejects_null_arguments);
|
||
|
|
|
||
|
|
AKSL_REPORT(failures);
|
||
|
|
}
|