518 lines
20 KiB
C
518 lines
20 KiB
C
|
|
/**
|
||
|
|
* @file value.c
|
||
|
|
* @brief Implements the BASIC value type and its operators.
|
||
|
|
*
|
||
|
|
* A faithful port of basicvalue.go. Where the reference does something
|
||
|
|
* arithmetically odd -- adding both of the right operand's numeric fields, for
|
||
|
|
* instance -- this reproduces it, because the golden corpus encodes the observed
|
||
|
|
* behaviour and a "fix" here is a silent behaviour change. Each one is catalogued
|
||
|
|
* in TODO.md section 12.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <inttypes.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/value.h>
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The reference writes `rval.intval + int64(rval.floatval)` on every integer
|
||
|
|
* operation and the mirror image on every float one. It works only because the
|
||
|
|
* unused field of a typed value is always zero. Named so the intent -- "whatever
|
||
|
|
* numeric payload the right operand is carrying" -- is legible, and so there is
|
||
|
|
* one place to change when TODO.md section 12 item 5 is fixed.
|
||
|
|
*/
|
||
|
|
static int64_t rval_as_int(akbasic_Value *rval)
|
||
|
|
{
|
||
|
|
return rval->intval + (int64_t)rval->floatval;
|
||
|
|
}
|
||
|
|
|
||
|
|
static double rval_as_float(akbasic_Value *rval)
|
||
|
|
{
|
||
|
|
return rval->floatval + (double)rval->intval;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copy a string into a value's inline buffer. Truncation is an error. */
|
||
|
|
static akerr_ErrorContext *set_string(akbasic_Value *dest, const char *src)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (strlen(src) < AKBASIC_MAX_STRING_LENGTH),
|
||
|
|
AKBASIC_ERR_VALUE,
|
||
|
|
"String result of %zu characters exceeds the %d character limit",
|
||
|
|
strlen(src), AKBASIC_MAX_STRING_LENGTH - 1);
|
||
|
|
strncpy(dest->stringval, src, AKBASIC_MAX_STRING_LENGTH - 1);
|
||
|
|
dest->stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_valuepool_init(akbasic_ValuePool *obj)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL pool in init");
|
||
|
|
memset(obj, 0, sizeof(*obj));
|
||
|
|
obj->next = 0;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_valuepool_take(akbasic_ValuePool *obj, int count, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL pool in take");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in take");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count > 0), AKBASIC_ERR_BOUNDS,
|
||
|
|
"Array element count %d must be positive", count);
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count <= AKBASIC_MAX_ARRAY_VALUES - obj->next),
|
||
|
|
AKBASIC_ERR_BOUNDS,
|
||
|
|
"Array of %d elements does not fit in the %d remaining value slots",
|
||
|
|
count, AKBASIC_MAX_ARRAY_VALUES - obj->next);
|
||
|
|
|
||
|
|
*dest = &obj->values[obj->next];
|
||
|
|
for ( i = 0; i < count; i++ ) {
|
||
|
|
PASS(errctx, akbasic_value_zero(&obj->values[obj->next + i]));
|
||
|
|
}
|
||
|
|
obj->next += count;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_init(akbasic_Value *obj)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL value in init");
|
||
|
|
/*
|
||
|
|
* BasicValue.init() is empty in the reference; the zeroing happens in
|
||
|
|
* zero(). Keeping both means the call sites port one-for-one.
|
||
|
|
*/
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_zero(akbasic_Value *obj)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL value in zero");
|
||
|
|
obj->valuetype = AKBASIC_TYPE_UNDEFINED;
|
||
|
|
obj->stringval[0] = '\0';
|
||
|
|
obj->mutable_ = false;
|
||
|
|
obj->intval = 0;
|
||
|
|
obj->floatval = 0.0;
|
||
|
|
obj->boolvalue = AKBASIC_FALSE;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL source in clone");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in clone");
|
||
|
|
|
||
|
|
if ( self == dest ) {
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
dest->valuetype = self->valuetype;
|
||
|
|
memcpy(dest->stringval, self->stringval, sizeof(dest->stringval));
|
||
|
|
dest->intval = self->intval;
|
||
|
|
dest->floatval = self->floatval;
|
||
|
|
dest->boolvalue = self->boolvalue;
|
||
|
|
/* mutable_ is deliberately not copied: the reference's clone() does not. */
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_to_string(akbasic_Value *self, char *dest, size_t len)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in to_string");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in to_string");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (len > 0), AKBASIC_ERR_BOUNDS, "Zero-length destination in to_string");
|
||
|
|
|
||
|
|
switch ( self->valuetype ) {
|
||
|
|
case AKBASIC_TYPE_STRING:
|
||
|
|
snprintf(dest, len, "%s", self->stringval);
|
||
|
|
break;
|
||
|
|
case AKBASIC_TYPE_INTEGER:
|
||
|
|
snprintf(dest, len, "%" PRId64, self->intval);
|
||
|
|
break;
|
||
|
|
case AKBASIC_TYPE_FLOAT:
|
||
|
|
snprintf(dest, len, "%f", self->floatval);
|
||
|
|
break;
|
||
|
|
case AKBASIC_TYPE_BOOLEAN:
|
||
|
|
/* Go's %t, which is "true"/"false" and not the numeric -1/0. */
|
||
|
|
snprintf(dest, len, "%s", (self->boolvalue == AKBASIC_TRUE ? "true" : "false"));
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
snprintf(dest, len, "(UNDEFINED STRING REPRESENTATION FOR %d)", (int)self->valuetype);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_set_bool(akbasic_Value *obj, bool result)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL value in set_bool");
|
||
|
|
obj->valuetype = AKBASIC_TYPE_BOOLEAN;
|
||
|
|
obj->boolvalue = (result ? AKBASIC_TRUE : AKBASIC_FALSE);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool akbasic_value_is_true(akbasic_Value *self)
|
||
|
|
{
|
||
|
|
if ( self == NULL || self->valuetype != AKBASIC_TYPE_BOOLEAN ) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return (self->boolvalue == AKBASIC_TRUE);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Shared prologue for the unary operators: validate, clone into scratch. */
|
||
|
|
static akerr_ErrorContext *unary_prologue(akbasic_Value *self, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in unary operation");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (scratch != NULL), AKERR_NULLPOINTER, "NULL scratch in unary operation");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in unary operation");
|
||
|
|
PASS(errctx, akbasic_value_clone(self, scratch));
|
||
|
|
*dest = scratch;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Shared prologue for the binary operators: validate, clone into scratch. */
|
||
|
|
static akerr_ErrorContext *binary_prologue(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in binary operation");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (scratch != NULL), AKERR_NULLPOINTER, "NULL scratch in binary operation");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in binary operation");
|
||
|
|
PASS(errctx, akbasic_value_clone(self, scratch));
|
||
|
|
*dest = scratch;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_invert(akbasic_Value *self, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in invert");
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (self->valuetype == AKBASIC_TYPE_STRING),
|
||
|
|
AKBASIC_ERR_TYPE, "Cannot invert a string");
|
||
|
|
PASS(errctx, unary_prologue(self, scratch, dest));
|
||
|
|
(*dest)->intval = -(self->intval);
|
||
|
|
(*dest)->floatval = -(self->floatval);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_bitwise_not(akbasic_Value *self, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in bitwise not");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self->valuetype == AKBASIC_TYPE_INTEGER),
|
||
|
|
AKBASIC_ERR_TYPE, "Cannot only perform bitwise operations on integers");
|
||
|
|
PASS(errctx, unary_prologue(self, scratch, dest));
|
||
|
|
(*dest)->intval = ~(self->intval);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_shift_left(akbasic_Value *self, int64_t bits, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in shift left");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self->valuetype == AKBASIC_TYPE_INTEGER),
|
||
|
|
AKBASIC_ERR_TYPE, "Only integer datatypes can be bit-shifted");
|
||
|
|
/*
|
||
|
|
* Go's << on a negative or >=64 count is defined; C's is undefined. Refuse
|
||
|
|
* rather than inherit the UB -- no golden case exercises it, so this cannot
|
||
|
|
* change observed behaviour.
|
||
|
|
*/
|
||
|
|
FAIL_ZERO_RETURN(errctx, (bits >= 0 && bits < 64), AKBASIC_ERR_VALUE,
|
||
|
|
"Shift count %" PRId64 " is out of range 0..63", bits);
|
||
|
|
PASS(errctx, unary_prologue(self, scratch, dest));
|
||
|
|
(*dest)->intval = (int64_t)((uint64_t)(*dest)->intval << bits);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_shift_right(akbasic_Value *self, int64_t bits, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in shift right");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self->valuetype == AKBASIC_TYPE_INTEGER),
|
||
|
|
AKBASIC_ERR_TYPE, "Only integer datatypes can be bit-shifted");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (bits >= 0 && bits < 64), AKBASIC_ERR_VALUE,
|
||
|
|
"Shift count %" PRId64 " is out of range 0..63", bits);
|
||
|
|
PASS(errctx, unary_prologue(self, scratch, dest));
|
||
|
|
(*dest)->intval = (*dest)->intval >> bits;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_bitwise_and(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in bitwise and");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self->valuetype == AKBASIC_TYPE_INTEGER),
|
||
|
|
AKBASIC_ERR_TYPE, "Cannot perform bitwise operations on string or float");
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
(*dest)->intval = self->intval & rval->intval;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_bitwise_or(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in bitwise or");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self->valuetype == AKBASIC_TYPE_INTEGER),
|
||
|
|
AKBASIC_ERR_TYPE, "Can only perform bitwise operations on integers");
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
(*dest)->intval = self->intval | rval->intval;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_bitwise_xor(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in bitwise xor");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval");
|
||
|
|
FAIL_ZERO_RETURN(errctx,
|
||
|
|
(self->valuetype == AKBASIC_TYPE_INTEGER && rval->valuetype == AKBASIC_TYPE_INTEGER),
|
||
|
|
AKBASIC_ERR_TYPE, "Can only perform bitwise operations on integers");
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
(*dest)->intval = self->intval ^ rval->intval;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_Value *out = NULL;
|
||
|
|
char buf[AKBASIC_MAX_STRING_LENGTH * 2];
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in math plus");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (scratch != NULL), AKERR_NULLPOINTER, "NULL scratch in math plus");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in math plus");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The asymmetry with every other operator is deliberate and load-bearing:
|
||
|
|
* mathPlus mutates self in place when self is mutable, and CommandNEXT's
|
||
|
|
* loop increment relies on that to advance the loop variable. TODO.md
|
||
|
|
* section 12 item 4.
|
||
|
|
*/
|
||
|
|
if ( !self->mutable_ ) {
|
||
|
|
PASS(errctx, akbasic_value_clone(self, scratch));
|
||
|
|
out = scratch;
|
||
|
|
} else {
|
||
|
|
out = self;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
out->intval = self->intval + rval_as_int(rval);
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
out->floatval = self->floatval + rval_as_float(rval);
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_STRING ) {
|
||
|
|
snprintf(buf, sizeof(buf), "%s%s", self->stringval, rval->stringval);
|
||
|
|
PASS(errctx, set_string(out, buf));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
snprintf(buf, sizeof(buf), "%s%" PRId64, self->stringval, rval->intval);
|
||
|
|
PASS(errctx, set_string(out, buf));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
snprintf(buf, sizeof(buf), "%s%f", self->stringval, rval->floatval);
|
||
|
|
PASS(errctx, set_string(out, buf));
|
||
|
|
} else {
|
||
|
|
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE, "Invalid arithmetic operation");
|
||
|
|
}
|
||
|
|
*dest = out;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_math_minus(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
FAIL_NONZERO_RETURN(errctx,
|
||
|
|
(self->valuetype == AKBASIC_TYPE_STRING || rval->valuetype == AKBASIC_TYPE_STRING),
|
||
|
|
AKBASIC_ERR_TYPE, "Cannot perform subtraction on strings");
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
(*dest)->intval = self->intval - rval_as_int(rval);
|
||
|
|
} else {
|
||
|
|
(*dest)->floatval = self->floatval - rval_as_float(rval);
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_math_divide(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
FAIL_NONZERO_RETURN(errctx,
|
||
|
|
(self->valuetype == AKBASIC_TYPE_STRING || rval->valuetype == AKBASIC_TYPE_STRING),
|
||
|
|
AKBASIC_ERR_TYPE, "Cannot perform division on strings");
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
/*
|
||
|
|
* Integer division by zero is UB in C where Go panics. Neither is
|
||
|
|
* acceptable in a library, and no golden case divides by zero, so raise.
|
||
|
|
*/
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (rval_as_int(rval) == 0), AKBASIC_ERR_VALUE,
|
||
|
|
"DIVISION BY ZERO");
|
||
|
|
(*dest)->intval = self->intval / rval_as_int(rval);
|
||
|
|
} else {
|
||
|
|
(*dest)->floatval = self->floatval / rval_as_float(rval);
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_math_multiply(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
char buf[AKBASIC_MAX_STRING_LENGTH];
|
||
|
|
int64_t i = 0;
|
||
|
|
size_t srclen = 0;
|
||
|
|
size_t offset = 0;
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_STRING ) {
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (rval->valuetype == AKBASIC_TYPE_STRING),
|
||
|
|
AKBASIC_ERR_TYPE, "String multiplication requires an integer multiple");
|
||
|
|
/*
|
||
|
|
* Go's strings.Repeat panics on a negative count. Refusing is strictly
|
||
|
|
* better than either panicking or reading off the end of the buffer, and
|
||
|
|
* no golden case does it.
|
||
|
|
*/
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (rval->intval < 0), AKBASIC_ERR_VALUE,
|
||
|
|
"String multiplier %" PRId64 " must not be negative", rval->intval);
|
||
|
|
srclen = strlen((*dest)->stringval);
|
||
|
|
FAIL_NONZERO_RETURN(errctx,
|
||
|
|
(srclen != 0 && (uint64_t)rval->intval > (AKBASIC_MAX_STRING_LENGTH - 1) / srclen),
|
||
|
|
AKBASIC_ERR_VALUE,
|
||
|
|
"Repeated string of %zu x %" PRId64 " characters exceeds the %d character limit",
|
||
|
|
srclen, rval->intval, AKBASIC_MAX_STRING_LENGTH - 1);
|
||
|
|
for ( i = 0; i < rval->intval; i++ ) {
|
||
|
|
memcpy(buf + offset, (*dest)->stringval, srclen);
|
||
|
|
offset += srclen;
|
||
|
|
}
|
||
|
|
buf[offset] = '\0';
|
||
|
|
PASS(errctx, set_string(*dest, buf));
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
* Not an `else if`. The reference falls through to the numeric branches even
|
||
|
|
* for a string, where self->floatval is 0 and the write is a harmless no-op.
|
||
|
|
* Kept so the port is provably faithful.
|
||
|
|
*/
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
(*dest)->intval = self->intval * rval_as_int(rval);
|
||
|
|
} else {
|
||
|
|
(*dest)->floatval = self->floatval * rval_as_float(rval);
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->intval < rval_as_int(rval)));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval < rval_as_float(rval)));
|
||
|
|
} else {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) < 0));
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->intval <= rval_as_int(rval)));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval <= rval_as_float(rval)));
|
||
|
|
} else {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) <= 0));
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->intval > rval_as_int(rval)));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval > rval_as_float(rval)));
|
||
|
|
} else {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) > 0));
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->intval >= rval_as_int(rval)));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval >= rval_as_float(rval)));
|
||
|
|
} else {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) >= 0));
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->intval == rval_as_int(rval)));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval == rval_as_float(rval)));
|
||
|
|
} else {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) == 0));
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||
|
|
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->intval != rval_as_int(rval)));
|
||
|
|
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval != rval_as_float(rval)));
|
||
|
|
} else {
|
||
|
|
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) != 0));
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|