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