127 lines
4.3 KiB
C
127 lines
4.3 KiB
C
|
|
/**
|
||
|
|
* @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 <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/value.h>
|
||
|
|
|
||
|
|
#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;
|
||
|
|
}
|