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:
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <akerror.h>
|
||||
|
||||
@@ -553,11 +554,67 @@ akerr_ErrorContext *akbasic_parse_label(akbasic_Parser *parser, akbasic_ASTLeaf
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
||||
/**
|
||||
* @brief `DIM NAME(SIZES)`, or `DIM NAME@ AS TYPE` / `DIM NAME@ AS PTR TO TYPE`.
|
||||
*
|
||||
* The `AS` clause is read here and parked on the *command* leaf: `literal_string`
|
||||
* takes the type name and `literal_int` records whether `PTR TO` was written.
|
||||
* Both fields are free on a command leaf, which is the same reasoning the field
|
||||
* leaf uses for `.left` -- this file's header note about three defects from one
|
||||
* overloaded link field is about giving a field two meanings on the *same* leaf
|
||||
* type, not about using an unused one.
|
||||
*
|
||||
* `AS`, `PTR` and `TO` are matched by lexeme rather than promoted to keywords.
|
||||
* Making them reserved words would break any existing program with a variable
|
||||
* called `AS#`, and they are only meaningful in this one position.
|
||||
*/
|
||||
/**
|
||||
* @brief `TYPE NAME`, whose body was already read by the prescan.
|
||||
*
|
||||
* Only the opening line is ever parsed: the verb jumps past its own `END TYPE`,
|
||||
* so the field lines never reach the parser at all. That is deliberate -- a
|
||||
* field line is a declaration, and `W#` on its own would otherwise evaluate as a
|
||||
* bare identifier and quietly create a global called `W#`.
|
||||
*/
|
||||
akerr_ErrorContext *akbasic_parse_type(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
PASS(errctx, parse_verb_with_identifier(parser, "TYPE", dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_ASTLeaf *command = NULL;
|
||||
akbasic_Token *word = NULL;
|
||||
|
||||
PASS(errctx, parse_verb_with_identifier(parser, "DIM", dest));
|
||||
command = *dest;
|
||||
|
||||
if ( !akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER) ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AS") == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected AS after DIM %s, not %s", command->right->identifier, word->lexeme);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected a type name after AS");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
if ( strcasecmp(word->lexeme, "PTR") == 0 ) {
|
||||
command->literal_int = 1;
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected TO after PTR");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "TO") == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected PTR TO TYPENAME, not PTR %s", word->lexeme);
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
}
|
||||
snprintf(command->literal_string, sizeof(command->literal_string), "%s", word->lexeme);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user