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> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
@@ -357,6 +357,66 @@ static akerr_ErrorContext *collect_subscripts(akbasic_Environment *obj, akbasic_
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assign into a field, checking the field's declared type as it goes.
|
||||
*
|
||||
* The type check is the same one a variable gets, and it comes from the same
|
||||
* place: the field's suffix said what it holds when the TYPE was declared. What
|
||||
* is different is that a *field* name is checked for existence at all, which a
|
||||
* variable name never is -- the set of fields is closed and written down, and
|
||||
* akbasic_structtype_field() lists them when one is missed.
|
||||
*/
|
||||
static akerr_ErrorContext *assign_field(akbasic_Environment *obj, akbasic_ASTLeaf *lval,
|
||||
akbasic_Value *rval, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_StructField *field = NULL;
|
||||
akbasic_Value *slot = NULL;
|
||||
|
||||
PASS(errctx, akbasic_struct_resolve(obj->runtime, lval, &field, &slot));
|
||||
|
||||
switch ( field->kind ) {
|
||||
case AKBASIC_FIELD_STRUCT:
|
||||
FAIL_ZERO_RETURN(errctx, (rval->valuetype == AKBASIC_TYPE_STRUCT), AKBASIC_ERR_TYPE,
|
||||
"Incompatible types in assignment to %s", field->name);
|
||||
FAIL_ZERO_RETURN(errctx, (rval->structtype == field->typeindex), AKBASIC_ERR_TYPE,
|
||||
"%s is a %s and cannot be assigned a %s", field->name,
|
||||
obj->runtime->structtypes.types[field->typeindex].name,
|
||||
obj->runtime->structtypes.types[rval->structtype].name);
|
||||
PASS(errctx, akbasic_struct_copy(obj->runtime, field->typeindex, rval->structbase, slot));
|
||||
break;
|
||||
case AKBASIC_FIELD_POINTER:
|
||||
FAIL_ZERO_RETURN(errctx, (rval->valuetype == AKBASIC_TYPE_POINTER), AKBASIC_ERR_TYPE,
|
||||
"%s is a pointer; POINT it AT a structure rather than assigning one to it",
|
||||
field->name);
|
||||
FAIL_ZERO_RETURN(errctx, (rval->structtype == field->typeindex), AKBASIC_ERR_TYPE,
|
||||
"%s points to %s and cannot hold a pointer to %s", field->name,
|
||||
obj->runtime->structtypes.types[field->typeindex].name,
|
||||
obj->runtime->structtypes.types[rval->structtype].name);
|
||||
PASS(errctx, akbasic_value_clone(rval, slot));
|
||||
break;
|
||||
default:
|
||||
if ( field->valuetype == AKBASIC_TYPE_INTEGER && rval->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
PASS(errctx, akbasic_value_zero(slot));
|
||||
slot->valuetype = AKBASIC_TYPE_INTEGER;
|
||||
slot->intval = (int64_t)rval->floatval;
|
||||
break;
|
||||
}
|
||||
if ( field->valuetype == AKBASIC_TYPE_FLOAT && rval->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
PASS(errctx, akbasic_value_zero(slot));
|
||||
slot->valuetype = AKBASIC_TYPE_FLOAT;
|
||||
slot->floatval = (double)rval->intval;
|
||||
break;
|
||||
}
|
||||
FAIL_ZERO_RETURN(errctx, (rval->valuetype == field->valuetype), AKBASIC_ERR_TYPE,
|
||||
"Incompatible types in assignment to %s", field->name);
|
||||
PASS(errctx, akbasic_value_clone(rval, slot));
|
||||
break;
|
||||
}
|
||||
*dest = slot;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_environment_assign(akbasic_Environment *obj, akbasic_ASTLeaf *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -368,6 +428,16 @@ akerr_ErrorContext *akbasic_environment_assign(akbasic_Environment *obj, akbasic
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && lval != NULL && rval != NULL && dest != NULL),
|
||||
AKERR_NULLPOINTER, "nil pointer");
|
||||
|
||||
/*
|
||||
* A field is addressed by walking the chain, not by looking a name up in a
|
||||
* scope -- `E@.POS@.X#` names no variable called `X#`. So it is resolved and
|
||||
* assigned here, before the by-name lookup the rest of this function does.
|
||||
*/
|
||||
if ( lval->leaftype == AKBASIC_LEAF_FIELD ) {
|
||||
PASS(errctx, assign_field(obj, lval, rval, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
PASS(errctx, akbasic_environment_get(obj, lval->identifier, &variable));
|
||||
FAIL_ZERO_RETURN(errctx, (variable != NULL), AKBASIC_ERR_UNDEFINED,
|
||||
"Identifier %s is undefined", lval->identifier);
|
||||
@@ -382,6 +452,46 @@ akerr_ErrorContext *akbasic_environment_assign(akbasic_Environment *obj, akbasic
|
||||
PASS(errctx, akbasic_variable_get_subscript(variable, subscripts, subscriptcount, &slot));
|
||||
|
||||
switch ( lval->leaftype ) {
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRUCT:
|
||||
/*
|
||||
* **This is the whole of copy-on-assign**, and it has to happen here
|
||||
* rather than in akbasic_value_clone(): a clone copies one slot, and one
|
||||
* slot holds a *reference* to an instance rather than the instance. Going
|
||||
* through clone would therefore alias -- exactly the reference semantics
|
||||
* the language does not have -- so a structure is intercepted before it
|
||||
* reaches that path and its slots are copied one at a time.
|
||||
*
|
||||
* A pointer variable is the opposite and takes the clone: copying a
|
||||
* pointer copies the reference, which is what makes `POINT` the only way
|
||||
* to share and assignment always a copy.
|
||||
*/
|
||||
FAIL_ZERO_RETURN(errctx, (variable->structtype >= 0), AKBASIC_ERR_STATE,
|
||||
"%s has not been DIMmed AS a type", lval->identifier);
|
||||
if ( variable->ispointer ) {
|
||||
FAIL_ZERO_RETURN(errctx, (rval->valuetype == AKBASIC_TYPE_POINTER),
|
||||
AKBASIC_ERR_TYPE,
|
||||
"%s is a pointer; POINT it AT a structure rather than assigning one to it",
|
||||
lval->identifier);
|
||||
FAIL_ZERO_RETURN(errctx, (rval->structtype == variable->structtype),
|
||||
AKBASIC_ERR_TYPE,
|
||||
"%s points to %s and cannot hold a pointer to %s",
|
||||
lval->identifier,
|
||||
obj->runtime->structtypes.types[variable->structtype].name,
|
||||
obj->runtime->structtypes.types[rval->structtype].name);
|
||||
PASS(errctx, akbasic_value_clone(rval, slot));
|
||||
break;
|
||||
}
|
||||
FAIL_ZERO_RETURN(errctx, (rval->valuetype == AKBASIC_TYPE_STRUCT), AKBASIC_ERR_TYPE,
|
||||
"Incompatible types in variable assignment");
|
||||
FAIL_ZERO_RETURN(errctx, (rval->structtype == variable->structtype), AKBASIC_ERR_TYPE,
|
||||
"%s is a %s and cannot be assigned a %s",
|
||||
lval->identifier,
|
||||
obj->runtime->structtypes.types[variable->structtype].name,
|
||||
obj->runtime->structtypes.types[rval->structtype].name);
|
||||
PASS(errctx, akbasic_struct_copy(obj->runtime, variable->structtype,
|
||||
rval->structbase, slot));
|
||||
*dest = slot;
|
||||
SUCCEED_RETURN(errctx);
|
||||
case AKBASIC_LEAF_IDENTIFIER_INT:
|
||||
if ( rval->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
PASS(errctx, akbasic_variable_set_integer(variable, rval->intval, subscripts, subscriptcount));
|
||||
|
||||
Reference in New Issue
Block a user