diff --git a/TODO.md b/TODO.md index 7cf3c12..cfb9843 100644 --- a/TODO.md +++ b/TODO.md @@ -1016,10 +1016,10 @@ entire test corpus and passes clean under ASan and UBSan. | Gate | Result | |---|---| -| `ctest` | 75/75 — 41 upstream golden cases, 8 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` | 76/76 — 41 upstream golden cases, 9 local ones, 23 unit tests, 2 embedding examples, 1 known-failing | +| `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 | -| ASan + UBSan | 75/75 | +| ASan + UBSan | 76/76 | | Line coverage | 93.6% (3618/3867) — above the 90% gate | | Function coverage | 97.8% (267/273) | | Warnings | none under `-Wall -Wextra` | diff --git a/include/akbasic/grammar.h b/include/akbasic/grammar.h index 6c02fc0..daa833f 100644 --- a/include/akbasic/grammar.h +++ b/include/akbasic/grammar.h @@ -106,6 +106,26 @@ typedef struct akbasic_ASTLeaf struct akbasic_ASTLeaf *left; struct akbasic_ASTLeaf *right; 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; /** diff --git a/src/environment.c b/src/environment.c index b4263de..f84c824 100644 --- a/src/environment.c +++ b/src/environment.c @@ -329,10 +329,10 @@ static akerr_ErrorContext *collect_subscripts(akbasic_Environment *obj, akbasic_ akbasic_Value *tval = NULL; *count = 0; - if ( lval->right != NULL && - lval->right->leaftype == AKBASIC_LEAF_ARGUMENTLIST && - lval->right->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT ) { - for ( expr = lval->right->right; expr != NULL; expr = expr->right ) { + if ( lval->expr != NULL && + lval->expr->leaftype == AKBASIC_LEAF_ARGUMENTLIST && + lval->expr->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT ) { + for ( expr = lval->expr->right; expr != NULL; expr = expr->next ) { FAIL_ZERO_RETURN(errctx, (*count < AKBASIC_MAX_ARRAY_DEPTH), AKBASIC_ERR_BOUNDS, "More than %d array subscripts", AKBASIC_MAX_ARRAY_DEPTH); PASS(errctx, akbasic_runtime_evaluate(obj->runtime, expr, &tval)); diff --git a/src/grammar.c b/src/grammar.c index 6433c8a..7ffdfaf 100644 --- a/src/grammar.c +++ b/src/grammar.c @@ -49,6 +49,7 @@ akerr_ErrorContext *akbasic_leaf_init(akbasic_ASTLeaf *obj, akbasic_LeafType lea obj->left = NULL; obj->right = NULL; obj->expr = NULL; + obj->next = NULL; obj->identifier[0] = '\0'; obj->literal_int = 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->right, pool, ©->right)); PASS(errctx, clone_into(self->expr, pool, ©->expr)); + PASS(errctx, clone_into(self->next, pool, ©->next)); *dest = copy; 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) { if ( self == NULL || - self->right == NULL || - self->right->leaftype != AKBASIC_LEAF_ARGUMENTLIST || - self->right->operator_ != AKBASIC_TOK_ARRAY_SUBSCRIPT ) { + self->expr == NULL || + self->expr->leaftype != AKBASIC_LEAF_ARGUMENTLIST || + self->expr->operator_ != AKBASIC_TOK_ARRAY_SUBSCRIPT ) { return NULL; } - return self->right->right; + return self->expr->right; } bool akbasic_leaf_is_identifier(akbasic_ASTLeaf *self) diff --git a/src/parser.c b/src/parser.c index 2ae5312..3eb6b41 100644 --- a/src/parser.c +++ b/src/parser.c @@ -292,10 +292,16 @@ akerr_ErrorContext *akbasic_parser_argument_list(akbasic_Parser *obj, akbasic_To arglist->operator_ = arglisttype; 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; while ( expr != NULL && akbasic_parser_match1(obj, AKBASIC_TOK_COMMA) ) { - PASS(errctx, akbasic_parser_expression(obj, &expr->right)); - expr = expr->right; + PASS(errctx, akbasic_parser_expression(obj, &expr->next)); + expr = expr->next; } if ( !akbasic_parser_match1(obj, AKBASIC_TOK_RIGHT_PAREN) && requireparens ) { 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); } -/* 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) { akbasic_ASTLeaf *leaf = NULL; @@ -532,7 +538,7 @@ static int arglist_length(akbasic_ASTLeaf *arglist) if ( arglist == NULL ) { return 0; } - for ( leaf = arglist->right; leaf != NULL; leaf = leaf->right ) { + for ( leaf = arglist->right; leaf != NULL; leaf = leaf->next ) { count += 1; } return count; @@ -624,15 +630,15 @@ akerr_ErrorContext *akbasic_parser_primary(akbasic_Parser *obj, akbasic_ASTLeaf break; case AKBASIC_TOK_IDENTIFIER_INT: 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; case AKBASIC_TOK_IDENTIFIER_FLOAT: 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; case AKBASIC_TOK_IDENTIFIER_STRING: 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; case AKBASIC_TOK_FUNCTION: case AKBASIC_TOK_IDENTIFIER: diff --git a/src/parser_commands.c b/src/parser_commands.c index 9cf3eef..66214f4 100644 --- a/src/parser_commands.c +++ b/src/parser_commands.c @@ -69,8 +69,8 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf * "DRAW expected a color source and a coordinate"); tail = arglist->right; - while ( tail->right != NULL ) { - tail = tail->right; + while ( tail->next != NULL ) { + tail = tail->next; } 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), AKBASIC_ERR_SYNTAX, "DRAW expected TO"); - PASS(errctx, akbasic_parser_expression(parser, &tail->right)); - FAIL_ZERO_RETURN(errctx, (tail->right != NULL), AKBASIC_ERR_SYNTAX, + PASS(errctx, akbasic_parser_expression(parser, &tail->next)); + FAIL_ZERO_RETURN(errctx, (tail->next != NULL), AKBASIC_ERR_SYNTAX, "DRAW expected X after TO"); - tail = tail->right; + tail = tail->next; FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMA), AKBASIC_ERR_SYNTAX, "DRAW expected TO X,Y"); - PASS(errctx, akbasic_parser_expression(parser, &tail->right)); - FAIL_ZERO_RETURN(errctx, (tail->right != NULL), AKBASIC_ERR_SYNTAX, + PASS(errctx, akbasic_parser_expression(parser, &tail->next)); + FAIL_ZERO_RETURN(errctx, (tail->next != NULL), AKBASIC_ERR_SYNTAX, "DRAW expected Y after TO X,"); - tail = tail->right; + tail = tail->next; } 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, "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, (walk->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING || walk->leaftype == AKBASIC_LEAF_IDENTIFIER_INT || @@ -321,12 +321,12 @@ akerr_ErrorContext *akbasic_parse_read(akbasic_Parser *parser, akbasic_ASTLeaf * "Expected identifier"); PASS(errctx, akbasic_leaf_clone(expr, &env->readLeafPool, &env->readIdentifierLeaves[i])); /* - * A cloned identifier keeps its .right chain, which for READ is the - * *next* identifier, not a subscript. Sever it so evaluating this leaf - * cannot walk into its sibling. + * A cloned identifier keeps its sibling chain, which for READ is the + * *next* identifier. Sever it so evaluating this leaf cannot walk into + * its sibling. */ - env->readIdentifierLeaves[i]->right = NULL; - expr = expr->right; + env->readIdentifierLeaves[i]->next = NULL; + expr = expr->next; } 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)); FAIL_ZERO_RETURN(errctx, (arglist != NULL && arglist->right != NULL), AKBASIC_ERR_SYNTAX, "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, "Expected literal"); } diff --git a/src/runtime.c b/src/runtime.c index 06ea3bb..771fa96 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -308,15 +308,17 @@ static akerr_ErrorContext *evaluate_identifier(akbasic_Runtime *obj, akbasic_AST int subscriptcount = 0; /* - * A .right hanging off an identifier is an array subscript only when it is - * an ARRAY_SUBSCRIPT argument list; anything else belongs to the enclosing - * expression and must not be followed. + * An identifier's subscript list hangs off .expr, deliberately clear of + * .right -- which is where an argument list chains its arguments, and where + * 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 && texpr->leaftype == AKBASIC_LEAF_ARGUMENTLIST && 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), AKBASIC_ERR_BOUNDS, "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; PASS(errctx, akbasic_environment_assign(fndef->environment, argptr, argvalue, &unused)); obj->environment = callerenv; - leafptr = leafptr->right; - argptr = argptr->right; + leafptr = leafptr->next; + argptr = argptr->next; } obj->environment = fndef->environment; diff --git a/src/runtime_audio.c b/src/runtime_audio.c index 1d6c97d..f07c98c 100644 --- a/src/runtime_audio.c +++ b/src/runtime_audio.c @@ -68,7 +68,7 @@ static akerr_ErrorContext *collect_numbers(akbasic_Runtime *obj, akbasic_ASTLeaf values[*count] = (value->valuetype == AKBASIC_TYPE_FLOAT) ? value->floatval : (double)value->intval; *count += 1; - arg = arg->right; + arg = arg->next; } SUCCEED_RETURN(errctx); } diff --git a/src/runtime_commands.c b/src/runtime_commands.c index 739efeb..e723624 100644 --- a/src/runtime_commands.c +++ b/src/runtime_commands.c @@ -224,9 +224,9 @@ akerr_ErrorContext *akbasic_cmd_dim(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, (void)lval; (void)rval; FAIL_ZERO_RETURN(errctx, - (expr != NULL && expr->right != NULL && expr->right->right != NULL && - expr->right->right->leaftype == AKBASIC_LEAF_ARGUMENTLIST && - expr->right->right->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT && + (expr != NULL && expr->right != NULL && expr->right->expr != NULL && + expr->right->expr->leaftype == AKBASIC_LEAF_ARGUMENTLIST && + expr->right->expr->operator_ == AKBASIC_TOK_ARRAY_SUBSCRIPT && akbasic_leaf_is_identifier(expr->right)), 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, "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, "More than %d array dimensions", AKBASIC_MAX_ARRAY_DEPTH); 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, "POKE expected INTEGER, INTEGER"); FAIL_ZERO_RETURN(errctx, - (arg->right != NULL && - (arg->right->leaftype == AKBASIC_LEAF_LITERAL_INT || - arg->right->leaftype == AKBASIC_LEAF_IDENTIFIER_INT)), + (arg->next != NULL && + (arg->next->leaftype == AKBASIC_LEAF_LITERAL_INT || + arg->next->leaftype == AKBASIC_LEAF_IDENTIFIER_INT)), AKBASIC_ERR_SYNTAX, "POKE expected INTEGER, INTEGER"); FAIL_ZERO_RETURN(errctx, (addrval->intval != 0), AKBASIC_ERR_VALUE, "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)byteval->intval; 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, "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 ) { break; } diff --git a/src/runtime_functions.c b/src/runtime_functions.c index 71259e1..e63cb3b 100644 --- a/src/runtime_functions.c +++ b/src/runtime_functions.c @@ -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"); arg = akbasic_leaf_first_argument(expr); for ( i = 0; i < n && arg != NULL; i++ ) { - arg = arg->right; + arg = arg->next; } FAIL_ZERO_RETURN(errctx, (arg != NULL), AKBASIC_ERR_SYNTAX, "%s is missing argument %d", fname, n + 1); diff --git a/src/runtime_graphics.c b/src/runtime_graphics.c index 8ef81ea..5c2718e 100644 --- a/src/runtime_graphics.c +++ b/src/runtime_graphics.c @@ -99,7 +99,7 @@ static akerr_ErrorContext *collect_numbers(akbasic_Runtime *obj, akbasic_ASTLeaf values[*count] = (double)value->intval; } *count += 1; - arg = arg->right; + arg = arg->next; } 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)); FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKBASIC_ERR_UNDEFINED, "%s could not reach the variable %s", verb, arg->identifier); - *rest = arg->right; + *rest = arg->next; 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) ? value->floatval : (double)value->intval; count += 1; - arg = arg->right; + arg = arg->next; } FAIL_ZERO_RETURN(errctx, (count >= 2), AKBASIC_ERR_SYNTAX, "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) ? value->floatval : (double)value->intval; count += 1; - arg = arg->right; + arg = arg->next; } x = (count >= 1) ? coords[0] : obj->gfx.x; y = (count >= 2) ? coords[1] : obj->gfx.y; diff --git a/tests/language/arrays_in_parameter_lists.bas b/tests/language/arrays_in_parameter_lists.bas new file mode 100644 index 0000000..06ae3e2 --- /dev/null +++ b/tests/language/arrays_in_parameter_lists.bas @@ -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# diff --git a/tests/language/arrays_in_parameter_lists.txt b/tests/language/arrays_in_parameter_lists.txt new file mode 100644 index 0000000..a732113 --- /dev/null +++ b/tests/language/arrays_in_parameter_lists.txt @@ -0,0 +1,7 @@ +9 +3 +1 +5 +2 +WORLD +99 diff --git a/tests/runtime_evaluate.c b/tests/runtime_evaluate.c index 698f39e..77812a7 100644 --- a/tests/runtime_evaluate.c +++ b/tests/runtime_evaluate.c @@ -132,6 +132,28 @@ int main(void) expect_int("A# = SHL(-1, 0)", -1); expect_int("A# = MOD(-7, 3)", -1); 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# = SHL(1, 4)", 16); expect_int("A# = SHR(16, 4)", 1);