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