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:
@@ -140,7 +140,8 @@ bool akbasic_leaf_is_identifier(akbasic_ASTLeaf *self)
|
||||
(self->leaftype == AKBASIC_LEAF_IDENTIFIER ||
|
||||
self->leaftype == AKBASIC_LEAF_IDENTIFIER_INT ||
|
||||
self->leaftype == AKBASIC_LEAF_IDENTIFIER_FLOAT ||
|
||||
self->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING));
|
||||
self->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ||
|
||||
self->leaftype == AKBASIC_LEAF_IDENTIFIER_STRUCT));
|
||||
}
|
||||
|
||||
akbasic_Type akbasic_leaf_identifier_type(akbasic_ASTLeaf *self)
|
||||
@@ -155,6 +156,8 @@ akbasic_Type akbasic_leaf_identifier_type(akbasic_ASTLeaf *self)
|
||||
return AKBASIC_TYPE_FLOAT;
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRING:
|
||||
return AKBASIC_TYPE_STRING;
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRUCT:
|
||||
return AKBASIC_TYPE_STRUCT;
|
||||
default:
|
||||
/* A bare identifier is a label, and a label has no storage to fill. */
|
||||
return AKBASIC_TYPE_UNDEFINED;
|
||||
@@ -338,6 +341,27 @@ akerr_ErrorContext *akbasic_leaf_new_identifier(akbasic_ASTLeaf *obj, akbasic_Le
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_leaf_new_field(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *base, const char *name, akbasic_TokenType operator_)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && base != NULL), AKERR_NULLPOINTER,
|
||||
"NULL leaf in new_field");
|
||||
PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_FIELD));
|
||||
PASS(errctx, copy_bounded(obj->identifier, name, "field name"));
|
||||
/*
|
||||
* The base hangs off `.left`, which is free for this leaf type and is where
|
||||
* a unary leaf already keeps its operand. Deliberately not `.right` or
|
||||
* `.expr`: this header's own note records three separate defects that came
|
||||
* from giving one link field two meanings, and a field chain is the fourth
|
||||
* thing that would have wanted one.
|
||||
*/
|
||||
obj->left = base;
|
||||
base->parent = obj;
|
||||
obj->operator_ = operator_;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
static const char *operator_to_str(akbasic_TokenType op)
|
||||
{
|
||||
switch ( op ) {
|
||||
@@ -386,7 +410,12 @@ akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, si
|
||||
snprintf(dest, len, "%s", self->identifier);
|
||||
break;
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRUCT:
|
||||
snprintf(dest, len, "NOT IMPLEMENTED");
|
||||
snprintf(dest, len, "%s", self->identifier);
|
||||
break;
|
||||
case AKBASIC_LEAF_FIELD:
|
||||
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
|
||||
snprintf(dest, len, "%s%s%s", sub1,
|
||||
(self->operator_ == AKBASIC_TOK_ARROW ? "->" : "."), self->identifier);
|
||||
break;
|
||||
case AKBASIC_LEAF_UNARY:
|
||||
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
|
||||
|
||||
Reference in New Issue
Block a user