Give argument lists their own link, not each argument's .right
An identifier's subscript list, a unary's operand and a binary's right-hand side all lived in the same field the argument chain used, so ABS(-9), MOD(A#, B# + 12) and any array reference in a parameter list were counted as extra arguments and refused. Subscript lists move to .expr and arguments now chain through a dedicated .next. This is the third time the same collision was fixed; the first two moved it along rather than removing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
6
TODO.md
6
TODO.md
@@ -1016,10 +1016,10 @@ entire test corpus and passes clean under ASan and UBSan.
|
|||||||
|
|
||||||
| Gate | Result |
|
| Gate | Result |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `ctest` | 75/75 — 41 upstream golden cases, 8 local ones, 23 unit tests, 2 embedding examples, 1 known-failing |
|
| `ctest` | 76/76 — 41 upstream golden cases, 9 local ones, 23 unit tests, 2 embedding examples, 1 known-failing |
|
||||||
| `ctest` with `-DAKBASIC_WITH_AKGL=ON` | 74/74 — the same, minus the three `no_device` cases the SDL driver contradicts, plus `akgl_backends` and `akgl_frontend`; the `akgl_build` CI job |
|
| `ctest` with `-DAKBASIC_WITH_AKGL=ON` | 75/75 — the same, minus the three `no_device` cases the SDL driver contradicts, plus `akgl_backends` and `akgl_frontend`; the `akgl_build` CI job |
|
||||||
| Golden corpus | 41/41 byte-exact, driven in place from the submodule — **and 41/41 again through the SDL binary**, which is most of what proves the frontend changes no output |
|
| Golden corpus | 41/41 byte-exact, driven in place from the submodule — **and 41/41 again through the SDL binary**, which is most of what proves the frontend changes no output |
|
||||||
| ASan + UBSan | 75/75 |
|
| ASan + UBSan | 76/76 |
|
||||||
| Line coverage | 93.6% (3618/3867) — above the 90% gate |
|
| Line coverage | 93.6% (3618/3867) — above the 90% gate |
|
||||||
| Function coverage | 97.8% (267/273) |
|
| Function coverage | 97.8% (267/273) |
|
||||||
| Warnings | none under `-Wall -Wextra` |
|
| Warnings | none under `-Wall -Wextra` |
|
||||||
|
|||||||
@@ -106,6 +106,26 @@ typedef struct akbasic_ASTLeaf
|
|||||||
struct akbasic_ASTLeaf *left;
|
struct akbasic_ASTLeaf *left;
|
||||||
struct akbasic_ASTLeaf *right;
|
struct akbasic_ASTLeaf *right;
|
||||||
struct akbasic_ASTLeaf *expr;
|
struct akbasic_ASTLeaf *expr;
|
||||||
|
/**
|
||||||
|
* The next argument or subscript in a list, and nothing else.
|
||||||
|
*
|
||||||
|
* **This field exists because reusing `.right` for it was a bug.** The
|
||||||
|
* reference chains an argument list through each argument's `.right`
|
||||||
|
* (`basicparser.go`'s argumentList), which is also where a unary leaf keeps
|
||||||
|
* its operand, where a binary leaf keeps its right-hand side, and where an
|
||||||
|
* identifier used to keep its subscript list. The three meanings were
|
||||||
|
* indistinguishable, and every one of them produced a wrong answer:
|
||||||
|
*
|
||||||
|
* ABS(-9) counted as two arguments, refused
|
||||||
|
* MOD(A#, B# + 12) counted as three, refused
|
||||||
|
* MOD(-7, 3) the second argument overwrote the first's operand
|
||||||
|
*
|
||||||
|
* -- TODO.md section 6 item 13, and the "array references in parameter
|
||||||
|
* lists" item in section 4, which turned out to be the same defect twice.
|
||||||
|
* An argument list's *head* is still the list leaf's `.right`; only the
|
||||||
|
* sibling chain moved here.
|
||||||
|
*/
|
||||||
|
struct akbasic_ASTLeaf *next;
|
||||||
} akbasic_ASTLeaf;
|
} akbasic_ASTLeaf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -329,10 +329,10 @@ static akerr_ErrorContext *collect_subscripts(akbasic_Environment *obj, akbasic_
|
|||||||
akbasic_Value *tval = NULL;
|
akbasic_Value *tval = NULL;
|
||||||
|
|
||||||
*count = 0;
|
*count = 0;
|
||||||
if ( lval->right != NULL &&
|
if ( lval->expr != NULL &&
|
||||||
lval->right->leaftype == AKBASIC_LEAF_ARGUMENTLIST &&
|
lval->expr->leaftype == AKBASIC_LEAF_ARGUMENTLIST &&
|
||||||
lval->right->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT ) {
|
lval->expr->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT ) {
|
||||||
for ( expr = lval->right->right; expr != NULL; expr = expr->right ) {
|
for ( expr = lval->expr->right; expr != NULL; expr = expr->next ) {
|
||||||
FAIL_ZERO_RETURN(errctx, (*count < AKBASIC_MAX_ARRAY_DEPTH), AKBASIC_ERR_BOUNDS,
|
FAIL_ZERO_RETURN(errctx, (*count < AKBASIC_MAX_ARRAY_DEPTH), AKBASIC_ERR_BOUNDS,
|
||||||
"More than %d array subscripts", AKBASIC_MAX_ARRAY_DEPTH);
|
"More than %d array subscripts", AKBASIC_MAX_ARRAY_DEPTH);
|
||||||
PASS(errctx, akbasic_runtime_evaluate(obj->runtime, expr, &tval));
|
PASS(errctx, akbasic_runtime_evaluate(obj->runtime, expr, &tval));
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ akerr_ErrorContext *akbasic_leaf_init(akbasic_ASTLeaf *obj, akbasic_LeafType lea
|
|||||||
obj->left = NULL;
|
obj->left = NULL;
|
||||||
obj->right = NULL;
|
obj->right = NULL;
|
||||||
obj->expr = NULL;
|
obj->expr = NULL;
|
||||||
|
obj->next = NULL;
|
||||||
obj->identifier[0] = '\0';
|
obj->identifier[0] = '\0';
|
||||||
obj->literal_int = 0;
|
obj->literal_int = 0;
|
||||||
obj->literal_float = 0.0;
|
obj->literal_float = 0.0;
|
||||||
@@ -96,6 +97,7 @@ static akerr_ErrorContext *clone_into(akbasic_ASTLeaf *self, akbasic_LeafPool *p
|
|||||||
PASS(errctx, clone_into(self->left, pool, ©->left));
|
PASS(errctx, clone_into(self->left, pool, ©->left));
|
||||||
PASS(errctx, clone_into(self->right, pool, ©->right));
|
PASS(errctx, clone_into(self->right, pool, ©->right));
|
||||||
PASS(errctx, clone_into(self->expr, pool, ©->expr));
|
PASS(errctx, clone_into(self->expr, pool, ©->expr));
|
||||||
|
PASS(errctx, clone_into(self->next, pool, ©->next));
|
||||||
|
|
||||||
*dest = copy;
|
*dest = copy;
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
@@ -124,12 +126,12 @@ akbasic_ASTLeaf *akbasic_leaf_first_argument(akbasic_ASTLeaf *self)
|
|||||||
akbasic_ASTLeaf *akbasic_leaf_first_subscript(akbasic_ASTLeaf *self)
|
akbasic_ASTLeaf *akbasic_leaf_first_subscript(akbasic_ASTLeaf *self)
|
||||||
{
|
{
|
||||||
if ( self == NULL ||
|
if ( self == NULL ||
|
||||||
self->right == NULL ||
|
self->expr == NULL ||
|
||||||
self->right->leaftype != AKBASIC_LEAF_ARGUMENTLIST ||
|
self->expr->leaftype != AKBASIC_LEAF_ARGUMENTLIST ||
|
||||||
self->right->operator_ != AKBASIC_TOK_ARRAY_SUBSCRIPT ) {
|
self->expr->operator_ != AKBASIC_TOK_ARRAY_SUBSCRIPT ) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return self->right->right;
|
return self->expr->right;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool akbasic_leaf_is_identifier(akbasic_ASTLeaf *self)
|
bool akbasic_leaf_is_identifier(akbasic_ASTLeaf *self)
|
||||||
|
|||||||
20
src/parser.c
20
src/parser.c
@@ -292,10 +292,16 @@ akerr_ErrorContext *akbasic_parser_argument_list(akbasic_Parser *obj, akbasic_To
|
|||||||
arglist->operator_ = arglisttype;
|
arglist->operator_ = arglisttype;
|
||||||
PASS(errctx, akbasic_parser_expression(obj, &arglist->right));
|
PASS(errctx, akbasic_parser_expression(obj, &arglist->right));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Siblings chain through .next, never through .right. Every leaf type that
|
||||||
|
* can be an argument already uses .right for something of its own -- a
|
||||||
|
* unary's operand, a binary's right-hand side -- and reusing it here is what
|
||||||
|
* made ABS(-9) count as two arguments. See akbasic_ASTLeaf.next.
|
||||||
|
*/
|
||||||
expr = arglist->right;
|
expr = arglist->right;
|
||||||
while ( expr != NULL && akbasic_parser_match1(obj, AKBASIC_TOK_COMMA) ) {
|
while ( expr != NULL && akbasic_parser_match1(obj, AKBASIC_TOK_COMMA) ) {
|
||||||
PASS(errctx, akbasic_parser_expression(obj, &expr->right));
|
PASS(errctx, akbasic_parser_expression(obj, &expr->next));
|
||||||
expr = expr->right;
|
expr = expr->next;
|
||||||
}
|
}
|
||||||
if ( !akbasic_parser_match1(obj, AKBASIC_TOK_RIGHT_PAREN) && requireparens ) {
|
if ( !akbasic_parser_match1(obj, AKBASIC_TOK_RIGHT_PAREN) && requireparens ) {
|
||||||
FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "Unbalanced parenthesis");
|
FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "Unbalanced parenthesis");
|
||||||
@@ -523,7 +529,7 @@ static akerr_ErrorContext *exponent(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Count the .right-joined chain hanging off an argument list. */
|
/* Count the arguments hanging off an argument list. */
|
||||||
static int arglist_length(akbasic_ASTLeaf *arglist)
|
static int arglist_length(akbasic_ASTLeaf *arglist)
|
||||||
{
|
{
|
||||||
akbasic_ASTLeaf *leaf = NULL;
|
akbasic_ASTLeaf *leaf = NULL;
|
||||||
@@ -532,7 +538,7 @@ static int arglist_length(akbasic_ASTLeaf *arglist)
|
|||||||
if ( arglist == NULL ) {
|
if ( arglist == NULL ) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
for ( leaf = arglist->right; leaf != NULL; leaf = leaf->right ) {
|
for ( leaf = arglist->right; leaf != NULL; leaf = leaf->next ) {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
@@ -624,15 +630,15 @@ akerr_ErrorContext *akbasic_parser_primary(akbasic_Parser *obj, akbasic_ASTLeaf
|
|||||||
break;
|
break;
|
||||||
case AKBASIC_TOK_IDENTIFIER_INT:
|
case AKBASIC_TOK_IDENTIFIER_INT:
|
||||||
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_INT, previous->lexeme));
|
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_INT, previous->lexeme));
|
||||||
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->right));
|
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
|
||||||
break;
|
break;
|
||||||
case AKBASIC_TOK_IDENTIFIER_FLOAT:
|
case AKBASIC_TOK_IDENTIFIER_FLOAT:
|
||||||
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_FLOAT, previous->lexeme));
|
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_FLOAT, previous->lexeme));
|
||||||
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->right));
|
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
|
||||||
break;
|
break;
|
||||||
case AKBASIC_TOK_IDENTIFIER_STRING:
|
case AKBASIC_TOK_IDENTIFIER_STRING:
|
||||||
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_STRING, previous->lexeme));
|
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_STRING, previous->lexeme));
|
||||||
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->right));
|
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
|
||||||
break;
|
break;
|
||||||
case AKBASIC_TOK_FUNCTION:
|
case AKBASIC_TOK_FUNCTION:
|
||||||
case AKBASIC_TOK_IDENTIFIER:
|
case AKBASIC_TOK_IDENTIFIER:
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf *
|
|||||||
"DRAW expected a color source and a coordinate");
|
"DRAW expected a color source and a coordinate");
|
||||||
|
|
||||||
tail = arglist->right;
|
tail = arglist->right;
|
||||||
while ( tail->right != NULL ) {
|
while ( tail->next != NULL ) {
|
||||||
tail = tail->right;
|
tail = tail->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( ;; ) {
|
for ( ;; ) {
|
||||||
@@ -82,16 +82,16 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf *
|
|||||||
}
|
}
|
||||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||||
AKBASIC_ERR_SYNTAX, "DRAW expected TO");
|
AKBASIC_ERR_SYNTAX, "DRAW expected TO");
|
||||||
PASS(errctx, akbasic_parser_expression(parser, &tail->right));
|
PASS(errctx, akbasic_parser_expression(parser, &tail->next));
|
||||||
FAIL_ZERO_RETURN(errctx, (tail->right != NULL), AKBASIC_ERR_SYNTAX,
|
FAIL_ZERO_RETURN(errctx, (tail->next != NULL), AKBASIC_ERR_SYNTAX,
|
||||||
"DRAW expected X after TO");
|
"DRAW expected X after TO");
|
||||||
tail = tail->right;
|
tail = tail->next;
|
||||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMA),
|
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMA),
|
||||||
AKBASIC_ERR_SYNTAX, "DRAW expected TO X,Y");
|
AKBASIC_ERR_SYNTAX, "DRAW expected TO X,Y");
|
||||||
PASS(errctx, akbasic_parser_expression(parser, &tail->right));
|
PASS(errctx, akbasic_parser_expression(parser, &tail->next));
|
||||||
FAIL_ZERO_RETURN(errctx, (tail->right != NULL), AKBASIC_ERR_SYNTAX,
|
FAIL_ZERO_RETURN(errctx, (tail->next != NULL), AKBASIC_ERR_SYNTAX,
|
||||||
"DRAW expected Y after TO X,");
|
"DRAW expected Y after TO X,");
|
||||||
tail = tail->right;
|
tail = tail->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
||||||
@@ -174,7 +174,7 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
|
|||||||
FAIL_ZERO_RETURN(errctx, (arglist != NULL), AKBASIC_ERR_SYNTAX,
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL), AKBASIC_ERR_SYNTAX,
|
||||||
"Expected argument list (identifier names)");
|
"Expected argument list (identifier names)");
|
||||||
|
|
||||||
for ( walk = arglist->right; walk != NULL; walk = walk->right ) {
|
for ( walk = arglist->right; walk != NULL; walk = walk->next ) {
|
||||||
FAIL_ZERO_RETURN(errctx,
|
FAIL_ZERO_RETURN(errctx,
|
||||||
(walk->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ||
|
(walk->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ||
|
||||||
walk->leaftype == AKBASIC_LEAF_IDENTIFIER_INT ||
|
walk->leaftype == AKBASIC_LEAF_IDENTIFIER_INT ||
|
||||||
@@ -321,12 +321,12 @@ akerr_ErrorContext *akbasic_parse_read(akbasic_Parser *parser, akbasic_ASTLeaf *
|
|||||||
"Expected identifier");
|
"Expected identifier");
|
||||||
PASS(errctx, akbasic_leaf_clone(expr, &env->readLeafPool, &env->readIdentifierLeaves[i]));
|
PASS(errctx, akbasic_leaf_clone(expr, &env->readLeafPool, &env->readIdentifierLeaves[i]));
|
||||||
/*
|
/*
|
||||||
* A cloned identifier keeps its .right chain, which for READ is the
|
* A cloned identifier keeps its sibling chain, which for READ is the
|
||||||
* *next* identifier, not a subscript. Sever it so evaluating this leaf
|
* *next* identifier. Sever it so evaluating this leaf cannot walk into
|
||||||
* cannot walk into its sibling.
|
* its sibling.
|
||||||
*/
|
*/
|
||||||
env->readIdentifierLeaves[i]->right = NULL;
|
env->readIdentifierLeaves[i]->next = NULL;
|
||||||
expr = expr->right;
|
expr = expr->next;
|
||||||
}
|
}
|
||||||
env->readReturnLine = env->lineno + 1;
|
env->readReturnLine = env->lineno + 1;
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ akerr_ErrorContext *akbasic_parse_data(akbasic_Parser *parser, akbasic_ASTLeaf *
|
|||||||
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, false, &arglist));
|
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, false, &arglist));
|
||||||
FAIL_ZERO_RETURN(errctx, (arglist != NULL && arglist->right != NULL), AKBASIC_ERR_SYNTAX,
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL && arglist->right != NULL), AKBASIC_ERR_SYNTAX,
|
||||||
"Expected literal");
|
"Expected literal");
|
||||||
for ( expr = arglist->right; expr != NULL; expr = expr->right ) {
|
for ( expr = arglist->right; expr != NULL; expr = expr->next ) {
|
||||||
FAIL_ZERO_RETURN(errctx, akbasic_leaf_is_literal(expr), AKBASIC_ERR_SYNTAX,
|
FAIL_ZERO_RETURN(errctx, akbasic_leaf_is_literal(expr), AKBASIC_ERR_SYNTAX,
|
||||||
"Expected literal");
|
"Expected literal");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,15 +308,17 @@ static akerr_ErrorContext *evaluate_identifier(akbasic_Runtime *obj, akbasic_AST
|
|||||||
int subscriptcount = 0;
|
int subscriptcount = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A .right hanging off an identifier is an array subscript only when it is
|
* An identifier's subscript list hangs off .expr, deliberately clear of
|
||||||
* an ARRAY_SUBSCRIPT argument list; anything else belongs to the enclosing
|
* .right -- which is where an argument list chains its arguments, and where
|
||||||
* expression and must not be followed.
|
* INPUT's parse handler would otherwise collide with it. Checked for the
|
||||||
|
* ARRAY_SUBSCRIPT operator anyway, because .expr means something else on the
|
||||||
|
* leaf types that use it for grouping.
|
||||||
*/
|
*/
|
||||||
texpr = expr->right;
|
texpr = expr->expr;
|
||||||
if ( texpr != NULL &&
|
if ( texpr != NULL &&
|
||||||
texpr->leaftype == AKBASIC_LEAF_ARGUMENTLIST &&
|
texpr->leaftype == AKBASIC_LEAF_ARGUMENTLIST &&
|
||||||
texpr->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT ) {
|
texpr->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT ) {
|
||||||
for ( texpr = texpr->right; texpr != NULL; texpr = texpr->right ) {
|
for ( texpr = texpr->right; texpr != NULL; texpr = texpr->next ) {
|
||||||
FAIL_ZERO_RETURN(errctx, (subscriptcount < AKBASIC_MAX_ARRAY_DEPTH),
|
FAIL_ZERO_RETURN(errctx, (subscriptcount < AKBASIC_MAX_ARRAY_DEPTH),
|
||||||
AKBASIC_ERR_BOUNDS,
|
AKBASIC_ERR_BOUNDS,
|
||||||
"More than %d array subscripts", AKBASIC_MAX_ARRAY_DEPTH);
|
"More than %d array subscripts", AKBASIC_MAX_ARRAY_DEPTH);
|
||||||
@@ -611,8 +613,8 @@ akerr_ErrorContext *akbasic_runtime_user_function(akbasic_Runtime *obj, akbasic_
|
|||||||
obj->environment = fndef->environment;
|
obj->environment = fndef->environment;
|
||||||
PASS(errctx, akbasic_environment_assign(fndef->environment, argptr, argvalue, &unused));
|
PASS(errctx, akbasic_environment_assign(fndef->environment, argptr, argvalue, &unused));
|
||||||
obj->environment = callerenv;
|
obj->environment = callerenv;
|
||||||
leafptr = leafptr->right;
|
leafptr = leafptr->next;
|
||||||
argptr = argptr->right;
|
argptr = argptr->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
obj->environment = fndef->environment;
|
obj->environment = fndef->environment;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ static akerr_ErrorContext *collect_numbers(akbasic_Runtime *obj, akbasic_ASTLeaf
|
|||||||
values[*count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
values[*count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
||||||
? value->floatval : (double)value->intval;
|
? value->floatval : (double)value->intval;
|
||||||
*count += 1;
|
*count += 1;
|
||||||
arg = arg->right;
|
arg = arg->next;
|
||||||
}
|
}
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,9 +224,9 @@ akerr_ErrorContext *akbasic_cmd_dim(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
|||||||
|
|
||||||
(void)lval; (void)rval;
|
(void)lval; (void)rval;
|
||||||
FAIL_ZERO_RETURN(errctx,
|
FAIL_ZERO_RETURN(errctx,
|
||||||
(expr != NULL && expr->right != NULL && expr->right->right != NULL &&
|
(expr != NULL && expr->right != NULL && expr->right->expr != NULL &&
|
||||||
expr->right->right->leaftype == AKBASIC_LEAF_ARGUMENTLIST &&
|
expr->right->expr->leaftype == AKBASIC_LEAF_ARGUMENTLIST &&
|
||||||
expr->right->right->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT &&
|
expr->right->expr->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT &&
|
||||||
akbasic_leaf_is_identifier(expr->right)),
|
akbasic_leaf_is_identifier(expr->right)),
|
||||||
AKBASIC_ERR_SYNTAX, "Expected DIM IDENTIFIER(DIMENSIONS, ...)");
|
AKBASIC_ERR_SYNTAX, "Expected DIM IDENTIFIER(DIMENSIONS, ...)");
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ akerr_ErrorContext *akbasic_cmd_dim(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
|||||||
FAIL_ZERO_RETURN(errctx, (variable != NULL), AKBASIC_ERR_UNDEFINED,
|
FAIL_ZERO_RETURN(errctx, (variable != NULL), AKBASIC_ERR_UNDEFINED,
|
||||||
"Unable to get variable for identifier %s", expr->right->identifier);
|
"Unable to get variable for identifier %s", expr->right->identifier);
|
||||||
|
|
||||||
for ( walk = expr->right->right->right; walk != NULL; walk = walk->right ) {
|
for ( walk = expr->right->expr->right; walk != NULL; walk = walk->next ) {
|
||||||
FAIL_ZERO_RETURN(errctx, (sizecount < AKBASIC_MAX_ARRAY_DEPTH), AKBASIC_ERR_BOUNDS,
|
FAIL_ZERO_RETURN(errctx, (sizecount < AKBASIC_MAX_ARRAY_DEPTH), AKBASIC_ERR_BOUNDS,
|
||||||
"More than %d array dimensions", AKBASIC_MAX_ARRAY_DEPTH);
|
"More than %d array dimensions", AKBASIC_MAX_ARRAY_DEPTH);
|
||||||
PASS(errctx, akbasic_runtime_evaluate(obj, walk, &size));
|
PASS(errctx, akbasic_runtime_evaluate(obj, walk, &size));
|
||||||
@@ -281,14 +281,14 @@ akerr_ErrorContext *akbasic_cmd_poke(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
|||||||
FAIL_NONZERO_RETURN(errctx, (addrval->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
|
FAIL_NONZERO_RETURN(errctx, (addrval->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
|
||||||
"POKE expected INTEGER, INTEGER");
|
"POKE expected INTEGER, INTEGER");
|
||||||
FAIL_ZERO_RETURN(errctx,
|
FAIL_ZERO_RETURN(errctx,
|
||||||
(arg->right != NULL &&
|
(arg->next != NULL &&
|
||||||
(arg->right->leaftype == AKBASIC_LEAF_LITERAL_INT ||
|
(arg->next->leaftype == AKBASIC_LEAF_LITERAL_INT ||
|
||||||
arg->right->leaftype == AKBASIC_LEAF_IDENTIFIER_INT)),
|
arg->next->leaftype == AKBASIC_LEAF_IDENTIFIER_INT)),
|
||||||
AKBASIC_ERR_SYNTAX, "POKE expected INTEGER, INTEGER");
|
AKBASIC_ERR_SYNTAX, "POKE expected INTEGER, INTEGER");
|
||||||
FAIL_ZERO_RETURN(errctx, (addrval->intval != 0), AKBASIC_ERR_VALUE,
|
FAIL_ZERO_RETURN(errctx, (addrval->intval != 0), AKBASIC_ERR_VALUE,
|
||||||
"POKE got NIL pointer or uninitialized variable");
|
"POKE got NIL pointer or uninitialized variable");
|
||||||
|
|
||||||
PASS(errctx, akbasic_runtime_evaluate(obj, arg->right, &byteval));
|
PASS(errctx, akbasic_runtime_evaluate(obj, arg->next, &byteval));
|
||||||
target = (uint8_t *)(uintptr_t)addrval->intval;
|
target = (uint8_t *)(uintptr_t)addrval->intval;
|
||||||
*target = (uint8_t)byteval->intval;
|
*target = (uint8_t)byteval->intval;
|
||||||
SUCCEED_TRUE(obj, dest);
|
SUCCEED_TRUE(obj, dest);
|
||||||
@@ -761,7 +761,7 @@ akerr_ErrorContext *akbasic_cmd_data(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
|||||||
FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKERR_NULLPOINTER,
|
FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKERR_NULLPOINTER,
|
||||||
"NIL expression or argument list");
|
"NIL expression or argument list");
|
||||||
|
|
||||||
for ( literal = expr->right->right; literal != NULL; literal = literal->right ) {
|
for ( literal = expr->right->right; literal != NULL; literal = literal->next ) {
|
||||||
if ( env->readIdentifierIdx >= AKBASIC_MAX_LEAVES ) {
|
if ( env->readIdentifierIdx >= AKBASIC_MAX_LEAVES ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ static akerr_ErrorContext *nth_arg(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
|||||||
FAIL_ZERO_RETURN(errctx, (expr != NULL), AKERR_NULLPOINTER, "NIL leaf");
|
FAIL_ZERO_RETURN(errctx, (expr != NULL), AKERR_NULLPOINTER, "NIL leaf");
|
||||||
arg = akbasic_leaf_first_argument(expr);
|
arg = akbasic_leaf_first_argument(expr);
|
||||||
for ( i = 0; i < n && arg != NULL; i++ ) {
|
for ( i = 0; i < n && arg != NULL; i++ ) {
|
||||||
arg = arg->right;
|
arg = arg->next;
|
||||||
}
|
}
|
||||||
FAIL_ZERO_RETURN(errctx, (arg != NULL), AKBASIC_ERR_SYNTAX,
|
FAIL_ZERO_RETURN(errctx, (arg != NULL), AKBASIC_ERR_SYNTAX,
|
||||||
"%s is missing argument %d", fname, n + 1);
|
"%s is missing argument %d", fname, n + 1);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ static akerr_ErrorContext *collect_numbers(akbasic_Runtime *obj, akbasic_ASTLeaf
|
|||||||
values[*count] = (double)value->intval;
|
values[*count] = (double)value->intval;
|
||||||
}
|
}
|
||||||
*count += 1;
|
*count += 1;
|
||||||
arg = arg->right;
|
arg = arg->next;
|
||||||
}
|
}
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
@@ -557,7 +557,7 @@ static akerr_ErrorContext *shape_variable(akbasic_Runtime *obj, akbasic_ASTLeaf
|
|||||||
PASS(errctx, akbasic_environment_get(obj->environment, arg->identifier, dest));
|
PASS(errctx, akbasic_environment_get(obj->environment, arg->identifier, dest));
|
||||||
FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKBASIC_ERR_UNDEFINED,
|
FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKBASIC_ERR_UNDEFINED,
|
||||||
"%s could not reach the variable %s", verb, arg->identifier);
|
"%s could not reach the variable %s", verb, arg->identifier);
|
||||||
*rest = arg->right;
|
*rest = arg->next;
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,7 +584,7 @@ akerr_ErrorContext *akbasic_cmd_sshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
|||||||
coords[count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
coords[count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
||||||
? value->floatval : (double)value->intval;
|
? value->floatval : (double)value->intval;
|
||||||
count += 1;
|
count += 1;
|
||||||
arg = arg->right;
|
arg = arg->next;
|
||||||
}
|
}
|
||||||
FAIL_ZERO_RETURN(errctx, (count >= 2), AKBASIC_ERR_SYNTAX,
|
FAIL_ZERO_RETURN(errctx, (count >= 2), AKBASIC_ERR_SYNTAX,
|
||||||
"SSHAPE expected A$, X1, Y1");
|
"SSHAPE expected A$, X1, Y1");
|
||||||
@@ -636,7 +636,7 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
|||||||
coords[count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
coords[count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
||||||
? value->floatval : (double)value->intval;
|
? value->floatval : (double)value->intval;
|
||||||
count += 1;
|
count += 1;
|
||||||
arg = arg->right;
|
arg = arg->next;
|
||||||
}
|
}
|
||||||
x = (count >= 1) ? coords[0] : obj->gfx.x;
|
x = (count >= 1) ? coords[0] : obj->gfx.x;
|
||||||
y = (count >= 2) ? coords[1] : obj->gfx.y;
|
y = (count >= 2) ? coords[1] : obj->gfx.y;
|
||||||
|
|||||||
19
tests/language/arrays_in_parameter_lists.bas
Normal file
19
tests/language/arrays_in_parameter_lists.bas
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
10 REM An array reference used as a function argument, and as one of several.
|
||||||
|
20 REM An identifier's subscript list used to hang off .right, which is also
|
||||||
|
30 REM where an argument list chains its arguments -- so the arity counter walked
|
||||||
|
40 REM straight into the subscripts and refused the call. TODO.md section 4.
|
||||||
|
50 DIM C#(4)
|
||||||
|
60 C#(1) = -9
|
||||||
|
70 C#(2) = 7
|
||||||
|
80 PRINT ABS(C#(1))
|
||||||
|
90 PRINT MOD(C#(2), 4)
|
||||||
|
100 PRINT MOD(C#(2), C#(1) + 12)
|
||||||
|
110 DIM S$(2)
|
||||||
|
120 S$(0) = "HELLO"
|
||||||
|
130 PRINT LEN(S$(0))
|
||||||
|
140 PRINT INSTR(S$(0), "LL")
|
||||||
|
150 REM And a READ into an array element alongside a scalar.
|
||||||
|
160 READ S$(1), D#
|
||||||
|
170 DATA "WORLD", 99
|
||||||
|
180 PRINT S$(1)
|
||||||
|
190 PRINT D#
|
||||||
7
tests/language/arrays_in_parameter_lists.txt
Normal file
7
tests/language/arrays_in_parameter_lists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
9
|
||||||
|
3
|
||||||
|
1
|
||||||
|
5
|
||||||
|
2
|
||||||
|
WORLD
|
||||||
|
99
|
||||||
@@ -132,6 +132,28 @@ int main(void)
|
|||||||
expect_int("A# = SHL(-1, 0)", -1);
|
expect_int("A# = SHL(-1, 0)", -1);
|
||||||
expect_int("A# = MOD(-7, 3)", -1);
|
expect_int("A# = MOD(-7, 3)", -1);
|
||||||
expect_int("A# = INSTR(\"HELLO\", \"L\")", 2);
|
expect_int("A# = INSTR(\"HELLO\", \"L\")", 2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An argument that is itself an expression, and one that is an array
|
||||||
|
* reference. Both used to be counted as several arguments, because the
|
||||||
|
* argument chain and the leaves' own `.right` were the same field: a binary
|
||||||
|
* argument's right-hand side and an identifier's subscript list both looked
|
||||||
|
* like more arguments. Arguments now chain through `.next`. TODO.md section
|
||||||
|
* 4, "array references in parameter lists", which turned out to be section 6
|
||||||
|
* item 13 a second time.
|
||||||
|
*/
|
||||||
|
expect_int("A# = MOD(7, 1 + 2)", 1);
|
||||||
|
expect_int("A# = SHL(1 + 1, 2 + 1)", 16);
|
||||||
|
expect_int("A# = ABS(0 - 4)", 4);
|
||||||
|
/*
|
||||||
|
* Subscript zero rather than a DIM: every scalar is really a one-element
|
||||||
|
* array, so `N#(0)` is a subscripted reference to an ordinary variable and
|
||||||
|
* exercises the same parse path a DIMmed array would, without needing one.
|
||||||
|
*/
|
||||||
|
TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
|
||||||
|
expect_int("N#(0) = -9", -9);
|
||||||
|
expect_int("A# = ABS(N#(0))", 9);
|
||||||
|
expect_int("A# = MOD(N#(0) + 16, 4)", 3);
|
||||||
expect_int("A# = LEN(\"HELLO\")", 5);
|
expect_int("A# = LEN(\"HELLO\")", 5);
|
||||||
expect_int("A# = SHL(1, 4)", 16);
|
expect_int("A# = SHL(1, 4)", 16);
|
||||||
expect_int("A# = SHR(16, 4)", 1);
|
expect_int("A# = SHR(16, 4)", 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user