Let DEF take structure parameters, and give call scopes back
DEF AREA(S@ AS RECT) = S@.W# * S@.H#
DEF POKEIT(P@ AS PTR TO RECT)
A parameter names its type, exactly as DIM does. A bare `DEF F(S@)` is refused:
@ says "a structure" without saying which, so it does not state a contract the
way S$ does, and accepting it would mean checking fields at the call rather than
at the declaration -- which is the hole naming the type closes. The cost is that
there are no generic functions, and that is a real loss rather than an oversight.
Passing is by value, because a parameter is bound by assignment and assignment
copies; a pointer parameter copies its reference and lets a function change its
caller's record on purpose. Neither is a special rule. What a structure
parameter does need is its storage prepared before the copy, since a structure
variable is a run of slots and there is nothing to copy into until the run
exists.
A DEF parameter list is no longer parsed as an argument list, because a
parameter is a declaration rather than an expression: `S@ AS RECT` stopped that
parser dead with "Unbalanced parenthesis".
akbasic_value_is_truthy() learned that a pointer is true when it points at
something, which had to come with this. Without it there is no way to test for
the end of a list at all -- comparing a pointer to 0 reads a numeric field it
does not carry and answers whatever that field held. A structure is deliberately
given no truth value: it always exists, so the question has no answer worth
guessing at.
And a regression I introduced last commit, plus the older one underneath it.
prev_environment() released a scope but not the variables the scope created, so
a call leaked one slot per parameter and two hundred calls exhausted the
128-slot pool. Giving each DEF call its own scope made that reachable; it was
there for GOSUB all along, measured on a stashed build -- a subroutine with a
local of its own failed after about 128 calls before any of this work. The
release is safe because a scope's table holds only what it created, and it is
the variable slot that comes back rather than its storage, so a pointer into a
record DIMmed in that scope stays sound.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -673,6 +673,90 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
* so the environment is told to skip forward to that RETURN rather than execute
|
||||
* the body during the definition.
|
||||
*/
|
||||
/**
|
||||
* @brief A `DEF` parameter list, which is not an ordinary argument list.
|
||||
*
|
||||
* `akbasic_parser_argument_list()` parses *expressions*, and a parameter is a
|
||||
* declaration -- `S@ AS RECT` is not an expression and stops that parser dead
|
||||
* with "Unbalanced parenthesis". So this reads the list itself.
|
||||
*
|
||||
* **A structure parameter must name its type.** `@` says "a structure" without
|
||||
* saying which: three primitive types fit in three suffix characters and N
|
||||
* declared types do not fit in one, so `X$` fully states its contract and `X@`
|
||||
* does not. A bare `X@` is refused rather than treated as "any structure",
|
||||
* because that is duck typing and it puts back exactly the hole naming the type
|
||||
* closes. The cost is that there are no generic functions, and it is a real one.
|
||||
*
|
||||
* The type name is parked on the parameter leaf in `literal_string`, with
|
||||
* `literal_int` recording `PTR TO` -- the same two free fields, for the same
|
||||
* reason, that `DIM ... AS` uses.
|
||||
*/
|
||||
static akerr_ErrorContext *parse_def_parameters(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_ASTLeaf *arglist = NULL;
|
||||
akbasic_ASTLeaf *param = NULL;
|
||||
akbasic_ASTLeaf *tail = NULL;
|
||||
akbasic_Token *word = NULL;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_LEFT_PAREN),
|
||||
AKBASIC_ERR_SYNTAX, "Expected an argument list after DEF <name>");
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
|
||||
arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
|
||||
arglist->operator_ = AKBASIC_TOK_FUNCTION_ARGUMENT;
|
||||
|
||||
do {
|
||||
PASS(errctx, akbasic_parser_primary(parser, ¶m));
|
||||
FAIL_ZERO_RETURN(errctx,
|
||||
(param != NULL &&
|
||||
(param->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ||
|
||||
param->leaftype == AKBASIC_LEAF_IDENTIFIER_INT ||
|
||||
param->leaftype == AKBASIC_LEAF_IDENTIFIER_FLOAT ||
|
||||
param->leaftype == AKBASIC_LEAF_IDENTIFIER_STRUCT)),
|
||||
AKBASIC_ERR_SYNTAX,
|
||||
"Only variable identifiers are valid arguments for DEF");
|
||||
|
||||
if ( param->leaftype == AKBASIC_LEAF_IDENTIFIER_STRUCT ) {
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX,
|
||||
"%s must name its type: %s AS TYPENAME, or %s AS PTR TO TYPENAME",
|
||||
param->identifier, param->identifier, param->identifier);
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AS") == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected AS after %s, not %s", param->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 ) {
|
||||
param->literal_int = 1;
|
||||
/* `TO` is already a verb -- FOR ... TO owns it -- so it arrives
|
||||
as a command token rather than an identifier. */
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||
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(param->literal_string, sizeof(param->literal_string), "%s", word->lexeme);
|
||||
}
|
||||
|
||||
if ( tail == NULL ) {
|
||||
arglist->right = param;
|
||||
} else {
|
||||
tail->next = param;
|
||||
}
|
||||
tail = param;
|
||||
} while ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMA) );
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_RIGHT_PAREN),
|
||||
AKBASIC_ERR_SYNTAX, "Expected ) after the argument list");
|
||||
*dest = arglist;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -680,7 +764,6 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
akbasic_ASTLeaf *identifier = NULL;
|
||||
akbasic_ASTLeaf *arglist = NULL;
|
||||
akbasic_ASTLeaf *expression = NULL;
|
||||
akbasic_ASTLeaf *walk = NULL;
|
||||
akbasic_ASTLeaf *command = NULL;
|
||||
akbasic_FunctionDef *fndef = NULL;
|
||||
size_t i = 0;
|
||||
@@ -689,18 +772,7 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
FAIL_ZERO_RETURN(errctx, (identifier->leaftype == AKBASIC_LEAF_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected identifier");
|
||||
|
||||
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, true, &arglist));
|
||||
FAIL_ZERO_RETURN(errctx, (arglist != NULL), AKBASIC_ERR_SYNTAX,
|
||||
"Expected argument list (identifier names)");
|
||||
|
||||
for ( walk = arglist->right; walk != NULL; walk = walk->next ) {
|
||||
FAIL_ZERO_RETURN(errctx,
|
||||
(walk->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ||
|
||||
walk->leaftype == AKBASIC_LEAF_IDENTIFIER_INT ||
|
||||
walk->leaftype == AKBASIC_LEAF_IDENTIFIER_FLOAT),
|
||||
AKBASIC_ERR_SYNTAX,
|
||||
"Only variable identifiers are valid arguments for DEF");
|
||||
}
|
||||
PASS(errctx, parse_def_parameters(parser, &arglist));
|
||||
|
||||
PASS(errctx, akbasic_runtime_new_function(runtime, &fndef));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user