103 lines
4.2 KiB
C
103 lines
4.2 KiB
C
|
|
/**
|
||
|
|
* @file numeric_contract.c
|
||
|
|
* @brief Pins the string-to-number contract this port depends on libakstdlib for.
|
||
|
|
*
|
||
|
|
* `src/convert.c` used to live here in spirit: it wrapped `strtoll`/`strtod`
|
||
|
|
* because `libakstdlib`'s `aksl_ato*` family could not report a conversion
|
||
|
|
* failure at all, and every assertion below is one that family used to get
|
||
|
|
* wrong. As of `libakstdlib` 0.2.0 it gets them right, so the wrapper is gone
|
||
|
|
* and the call sites go straight to the library.
|
||
|
|
*
|
||
|
|
* The assertions did not go with it. Four things in this interpreter turn a
|
||
|
|
* string into a number -- an integer literal, a float literal, a line number in
|
||
|
|
* the scanner, and `VAL()` at runtime -- and every one of them relies on a bad
|
||
|
|
* string being *refused* rather than quietly becoming zero. That is a contract
|
||
|
|
* belonging to another library now, which is exactly why it is worth a test on
|
||
|
|
* this side: if it ever regresses, `VAL("garbage")` starts returning 0 and
|
||
|
|
* nothing else in the suite would say so.
|
||
|
|
*
|
||
|
|
* Same reasoning `libakstdlib`'s own `tests/test_status_registry.c` gives for
|
||
|
|
* pinning that it reserves no status range: a contract you depend on and do not
|
||
|
|
* own is worth asserting where you depend on it.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <errno.h>
|
||
|
|
|
||
|
|
#include <akstdlib.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
|
||
|
|
#include "testutil.h"
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
long long i = 0;
|
||
|
|
double d = 0.0;
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(akbasic_error_register());
|
||
|
|
|
||
|
|
/* The shapes the scanner and the parser actually produce. */
|
||
|
|
TEST_REQUIRE_OK(aksl_atoll("1234", &i));
|
||
|
|
TEST_REQUIRE_INT(i, 1234);
|
||
|
|
TEST_REQUIRE_OK(aksl_atoll("-256", &i));
|
||
|
|
TEST_REQUIRE_INT(i, -256);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Hex goes through aksl_strtoll with an explicit base, because the ato*
|
||
|
|
* forms are base 10 and would stop at the 'x'. src/grammar.c picks the base
|
||
|
|
* from the lexeme's prefix and passes it here, so both halves matter: base
|
||
|
|
* 16 must accept the 0x, and base 8 must read a leading zero as octal --
|
||
|
|
* which is the reference's behaviour that TODO.md section 6 item 10 records
|
||
|
|
* as a defect and section 5 says to reproduce until it is fixed on purpose.
|
||
|
|
*/
|
||
|
|
TEST_REQUIRE_OK(aksl_strtoll("0xff", NULL, 16, &i));
|
||
|
|
TEST_REQUIRE_INT(i, 255);
|
||
|
|
TEST_REQUIRE_OK(aksl_strtoll("010", NULL, 8, &i));
|
||
|
|
TEST_REQUIRE_INT(i, 8);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The refusals. Every one of these returned success with 0 before
|
||
|
|
* libakstdlib 0.2.0, which is the whole reason src/convert.c existed: a
|
||
|
|
* silent 0 turns four diagnosable errors into wrong answers.
|
||
|
|
*/
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atoll("", &i), AKERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atoll("not a number", &i), AKERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atoll("12abc", &i), AKERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atoll("12 ", &i), AKERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atoll("99999999999999999999999", &i), ERANGE);
|
||
|
|
|
||
|
|
/* A NULL endptr is what makes trailing junk an error rather than a stop. */
|
||
|
|
TEST_REQUIRE_STATUS(aksl_strtoll("12abc", NULL, 10, &i), AKERR_VALUE);
|
||
|
|
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atoll(NULL, &i), AKERR_NULLPOINTER);
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atoll("1", NULL), AKERR_NULLPOINTER);
|
||
|
|
|
||
|
|
/* The float half, which is what VAL() and a float literal both reach. */
|
||
|
|
TEST_REQUIRE_OK(aksl_atof("123.456", &d));
|
||
|
|
TEST_REQUIRE_FEQ(d, 123.456);
|
||
|
|
TEST_REQUIRE_OK(aksl_atof("-2.5", &d));
|
||
|
|
TEST_REQUIRE_FEQ(d, -2.5);
|
||
|
|
TEST_REQUIRE_OK(aksl_atof("32", &d));
|
||
|
|
TEST_REQUIRE_FEQ(d, 32.0);
|
||
|
|
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atof("", &d), AKERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atof("garbage", &d), AKERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(aksl_atof("1.5x", &d), AKERR_VALUE);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* errno hygiene, kept from the wrapper's own tests because the hazard did
|
||
|
|
* not move: strtoll and strtod do not clear errno on success, so a stale
|
||
|
|
* ERANGE left by an earlier call must not make a valid conversion raise.
|
||
|
|
* Mutation testing found this when the wrapper owned it; asserting it here
|
||
|
|
* is what says libakstdlib carries the same guarantee.
|
||
|
|
*/
|
||
|
|
errno = ERANGE;
|
||
|
|
TEST_REQUIRE_OK(aksl_atoll("42", &i));
|
||
|
|
TEST_REQUIRE_INT(i, 42);
|
||
|
|
errno = ERANGE;
|
||
|
|
TEST_REQUIRE_OK(aksl_atof("2.5", &d));
|
||
|
|
TEST_REQUIRE_FEQ(d, 2.5);
|
||
|
|
|
||
|
|
return akbasic_test_failures;
|
||
|
|
}
|