Add records: TYPE, DIM ... AS, field access, copy on assign
BASIC 7.0 has no records at all, so none of this is a port. The `@` suffix was not invented either: the Go reference reserved IDENTIFIER_STRUCT and never used it, and src/grammar.c rendered such a leaf as "NOT IMPLEMENTED" until now. Declaring the type is what buys the storage model. A TYPE states its fields, so an instance has a known slot count and is laid out exactly as an array is -- one contiguous run from the same value pool DIM already draws from, with field access as offset arithmetic. No new pool holds data; the only new table holds descriptors. A nested value flattens into its container's run, which is why LINE with two POINTs and a string is five slots rather than three. Each field takes its type from its own suffix, the same rule every other name here follows, so a field list needs no type column. An `@` field is the exception and has to name its type, because three primitive types fit in three suffix characters and N declared types do not fit in one. The declaration is prescanned before the program runs, like labels and DATA and for the same reason: it has to be in effect wherever control goes. Three passes, each for a case the one before cannot do -- names first so a field can refer to a type declared later, then field lists, then sizes by repeated resolution. What never resolves is a cycle of by-value containment, so "a TYPE cannot contain itself by value" is a diagnosis rather than an assumption, and the message says to use PTR TO instead. Assignment copies. That interception is the whole feature and it cannot live in akbasic_value_clone(), which copies one slot -- and one slot holds a *reference* to an instance rather than the instance, so going through it would alias. A structure is intercepted before that path and its slots are copied one at a time, walking the descriptor rather than memcpy-ing the run, because a pointer field must copy its reference where a value field must copy its slots. Two smaller things the work required. All three prescans now sit inside one ATTEMPT: a malformed declaration is the program's mistake, and it was printing a stack trace and taking the driver with it, which is the boundary goal 3 exists to draw. And a fresh variable's structtype is -1 rather than the 0 a memset leaves, because 0 is a valid type index and every new variable was claiming to be the first type declared. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
20
src/value.c
20
src/value.c
@@ -59,15 +59,17 @@ static const char *TYPE_NAMES[] = {
|
||||
"an integer", /* AKBASIC_TYPE_INTEGER */
|
||||
"a float", /* AKBASIC_TYPE_FLOAT */
|
||||
"a string", /* AKBASIC_TYPE_STRING */
|
||||
"a truth value" /* AKBASIC_TYPE_BOOLEAN */
|
||||
"a truth value", /* AKBASIC_TYPE_BOOLEAN */
|
||||
"a structure", /* AKBASIC_TYPE_STRUCT */
|
||||
"a pointer" /* AKBASIC_TYPE_POINTER */
|
||||
};
|
||||
|
||||
typedef char akbasic_assert_type_names_complete
|
||||
[(sizeof(TYPE_NAMES) / sizeof(TYPE_NAMES[0]) == AKBASIC_TYPE_BOOLEAN + 1) ? 1 : -1];
|
||||
[(sizeof(TYPE_NAMES) / sizeof(TYPE_NAMES[0]) == AKBASIC_TYPE_POINTER + 1) ? 1 : -1];
|
||||
|
||||
static const char *type_name(akbasic_Type valuetype)
|
||||
{
|
||||
if ( valuetype < 0 || valuetype > AKBASIC_TYPE_BOOLEAN ) {
|
||||
if ( valuetype < 0 || valuetype > AKBASIC_TYPE_POINTER ) {
|
||||
return "a value of unknown type";
|
||||
}
|
||||
return TYPE_NAMES[valuetype];
|
||||
@@ -182,6 +184,8 @@ akerr_ErrorContext *akbasic_value_zero(akbasic_Value *obj)
|
||||
obj->intval = 0;
|
||||
obj->floatval = 0.0;
|
||||
obj->boolvalue = AKBASIC_FALSE;
|
||||
obj->structtype = -1;
|
||||
obj->structbase = NULL;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -200,6 +204,16 @@ akerr_ErrorContext *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest
|
||||
dest->intval = self->intval;
|
||||
dest->floatval = self->floatval;
|
||||
dest->boolvalue = self->boolvalue;
|
||||
/*
|
||||
* The structure reference copies with everything else, and for a POINTER
|
||||
* that is exactly right -- copying a pointer copies what it points at, not
|
||||
* what it points to. For a STRUCT it is *not* the copy the language
|
||||
* promises: assignment intercepts before it gets here and copies the slots
|
||||
* instead. Cloning the reference is still correct at this level, because
|
||||
* this is the one-slot copy and a structure does not fit in one slot.
|
||||
*/
|
||||
dest->structtype = self->structtype;
|
||||
dest->structbase = self->structbase;
|
||||
/* mutable_ is deliberately not copied: the reference's clone() does not. */
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user