/** * @file value_arithmetic.c * @brief Tests the arithmetic operators, including the parts that look wrong. * * Where the reference does something odd, the assertion here pins the reference's * answer, not the tidy one. A "fix" that changes any of these changes observable * BASIC behaviour and belongs in its own commit with its own TODO.md entry. */ #include #include #include #include "testutil.h" static akbasic_Value A; static akbasic_Value B; static akbasic_Value SCRATCH; static void set_int(akbasic_Value *v, int64_t n) { test_discard_error(akbasic_value_zero(v)); v->valuetype = AKBASIC_TYPE_INTEGER; v->intval = n; } static void set_float(akbasic_Value *v, double n) { test_discard_error(akbasic_value_zero(v)); v->valuetype = AKBASIC_TYPE_FLOAT; v->floatval = n; } static void set_string(akbasic_Value *v, const char *s) { test_discard_error(akbasic_value_zero(v)); v->valuetype = AKBASIC_TYPE_STRING; strncpy(v->stringval, s, AKBASIC_MAX_STRING_LENGTH - 1); } int main(void) { akbasic_Value *out = NULL; char rendered[AKBASIC_MAX_STRING_LENGTH]; TEST_REQUIRE_OK(akbasic_error_register()); /* Integer arithmetic. */ set_int(&A, 7); set_int(&B, 3); TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_INT(out->intval, 10); TEST_REQUIRE_OK(akbasic_value_math_minus(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_INT(out->intval, 4); TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_INT(out->intval, 21); TEST_REQUIRE_OK(akbasic_value_math_divide(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_INT(out->intval, 2); /* truncating, as C and Go both do */ /* Float arithmetic. */ set_float(&A, 1.20); set_float(&B, 0.4); TEST_REQUIRE_OK(akbasic_value_math_divide(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_OK(akbasic_value_to_string(out, rendered, sizeof(rendered))); /* tests/language/arithmetic/float.txt says 3.000000, not 2.999999. */ TEST_REQUIRE_STR(rendered, "3.000000"); /* String concatenation, including the mixed-type forms. */ set_string(&A, "SORTED IN "); set_int(&B, 4); TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_STR(out->stringval, "SORTED IN 4"); set_string(&A, "X="); set_float(&B, 2.5); TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_STR(out->stringval, "X=2.500000"); /* String multiplication is repetition. */ set_string(&A, "ab"); set_int(&B, 3); TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out)); TEST_REQUIRE_STR(out->stringval, "ababab"); /* * mathPlus mutates self in place when self is mutable, where every other * operator always clones. CommandNEXT's loop increment depends on it -- * TODO.md section 12 item 4. Pinning the asymmetry so a "cleanup" fails here * rather than silently breaking every FOR loop. */ set_int(&A, 10); A.mutable_ = true; set_int(&B, 5); TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out)); TEST_REQUIRE(out == &A, "math_plus on a mutable value must return self, not the scratch"); TEST_REQUIRE_INT(A.intval, 15); set_int(&A, 10); A.mutable_ = false; TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out)); TEST_REQUIRE(out == &SCRATCH, "math_plus on an immutable value must use the scratch"); TEST_REQUIRE_INT(A.intval, 10); /* Type errors. */ set_string(&A, "text"); set_int(&B, 1); TEST_REQUIRE_STATUS(akbasic_value_math_minus(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE); TEST_REQUIRE_STATUS(akbasic_value_math_divide(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE); TEST_REQUIRE_STATUS(akbasic_value_invert(&A, &SCRATCH, &out), AKBASIC_ERR_TYPE); /* Division by zero raises rather than trapping or panicking. */ set_int(&A, 1); set_int(&B, 0); TEST_REQUIRE_STATUS(akbasic_value_math_divide(&A, &B, &SCRATCH, &out), AKBASIC_ERR_VALUE); /* Unary minus. */ set_int(&A, 42); TEST_REQUIRE_OK(akbasic_value_invert(&A, &SCRATCH, &out)); TEST_REQUIRE_INT(out->intval, -42); /* nil rval is rejected everywhere. */ TEST_REQUIRE_STATUS(akbasic_value_math_plus(&A, NULL, &SCRATCH, &out), AKERR_NULLPOINTER); return akbasic_test_failures; }