/** * @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 #include #include #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; }