Files
akbasic/src/runtime_housekeeping.c

262 lines
10 KiB
C
Raw Normal View History

/**
* @file runtime_housekeeping.c
* @brief The housekeeping verbs: NEW, CLR, CONT, TRON, TROFF, SWAP and HELP.
*
* TODO.md section 4 group B. None of these is in the Go reference -- they are
* on its own "What Isn't Implemented" list -- so what they mean here is a
* decision rather than a transcription, and each decision is recorded at its
* verb. Where a C128's answer needs machine state this interpreter does not
* have, the verb does the part that means something and says so.
*
* None of them needs a device, and none is in the golden corpus's way: they all
* act on interpreter state a program can see through PRINT.
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akbasic/error.h>
#include <akbasic/runtime.h>
#include "verbs.h"
/* Most verbs answer "did something happen"; this is that answer. */
#define SUCCEED_TRUE(__obj, __dest) \
do { \
*(__dest) = &(__obj)->staticTrueValue; \
} while ( 0 )
/**
* @brief Drop every variable and function, and hand their storage back.
*
* Shared by NEW and CLR, which differ only in whether the program text goes
* too. The value pool is a bump allocator (see akbasic_ValuePool), so this is
* the only place anything it handed out ever comes back -- re-initializing the
* pool is what makes `CLR` in a loop viable where re-DIMming in one is not.
*/
static akerr_ErrorContext AKERR_NOIGNORE *clear_variables(akbasic_Runtime *obj)
{
PREPARE_ERROR(errctx);
akbasic_Environment *root = NULL;
int i = 0;
for ( i = 0; i < AKBASIC_MAX_VARIABLES; i++ ) {
obj->variables[i].used = false;
}
for ( i = 0; i < AKBASIC_MAX_FUNCTIONS; i++ ) {
obj->functions[i].used = false;
obj->functions[i].environment = NULL;
}
PASS(errctx, akbasic_valuepool_init(&obj->valuepool));
/*
* Unwind to the root before emptying its symbol tables. A CLR inside a
* GOSUB would otherwise leave child environments holding pointers into the
* variable pool it just released, and the parent chain is the only handle
* on them.
*/
while ( obj->environment != NULL && obj->environment->parent != NULL ) {
PASS(errctx, akbasic_runtime_prev_environment(obj));
}
root = obj->environment;
FAIL_ZERO_RETURN(errctx, (root != NULL), AKERR_NULLPOINTER, "Runtime has no root environment");
PASS(errctx, akbasic_symtab_init(&root->variables, AKBASIC_MAX_VARIABLES));
PASS(errctx, akbasic_symtab_init(&root->functions, AKBASIC_MAX_FUNCTIONS));
SUCCEED_RETURN(errctx);
}
/* ----------------------------------------------------------------- NEW --- */
akerr_ErrorContext *akbasic_cmd_new(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int64_t i = 0;
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in NEW");
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
obj->source[i].code[0] = '\0';
obj->source[i].lineno = 0;
}
PASS(errctx, clear_variables(obj));
PASS(errctx, akbasic_symtab_init(&obj->environment->labels, AKBASIC_MAX_LABELS));
/*
* The line counter goes home too. Without this a NEW typed at the REPL
* leaves the next entered line filed under wherever the last program
* stopped, which is a surprising place for a fresh program to start.
*/
obj->environment->lineno = 0;
obj->environment->nextline = 0;
obj->stopped = false;
obj->stoppedline = 0;
obj->errorline = 0;
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
/* ----------------------------------------------------------------- CLR --- */
akerr_ErrorContext *akbasic_cmd_clr(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in CLR");
/*
* Variables and functions, not the program. A C128's CLR also closes open
* files and empties the GOSUB and FOR stacks; the stacks *are* emptied here,
* because unwinding to the root is how the variable pool is made safe to
* release, and there are no open files to close -- DLOAD and DSAVE open,
* transfer and close within the verb.
*/
PASS(errctx, clear_variables(obj));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
/* ---------------------------------------------------------------- CONT --- */
akerr_ErrorContext *akbasic_cmd_cont(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in CONT");
/*
* Refused rather than treated as a RUN. "CAN'T CONTINUE" is what a C128
* says, and it says it for a reason: continuing a program that never
* started would run it from line zero with whatever variables happened to be
* lying about, which is not what anybody typing CONT wanted.
*/
FAIL_ZERO_RETURN(errctx, obj->stopped, AKBASIC_ERR_STATE, "CAN'T CONTINUE");
obj->environment->nextline = obj->stoppedline;
obj->stopped = false;
PASS(errctx, akbasic_runtime_set_mode(obj, AKBASIC_MODE_RUN));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
/* -------------------------------------------------------- TRON / TROFF --- */
akerr_ErrorContext *akbasic_cmd_tron(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in TRON");
obj->trace = true;
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_cmd_troff(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in TROFF");
obj->trace = false;
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
/* ---------------------------------------------------------------- SWAP --- */
akerr_ErrorContext *akbasic_cmd_swap(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *first = NULL;
akbasic_ASTLeaf *second = NULL;
akbasic_Variable *a = NULL;
akbasic_Variable *b = NULL;
akbasic_Variable swap;
char namea[AKBASIC_MAX_STRING_LENGTH];
char nameb[AKBASIC_MAX_STRING_LENGTH];
(void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && expr != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in SWAP");
first = akbasic_leaf_first_argument(expr);
second = (first != NULL ? first->next : NULL);
FAIL_ZERO_RETURN(errctx, (first != NULL && second != NULL), AKBASIC_ERR_SYNTAX,
"Expected SWAP VARIABLE, VARIABLE");
FAIL_ZERO_RETURN(errctx,
(akbasic_leaf_is_identifier(first) && akbasic_leaf_is_identifier(second)),
AKBASIC_ERR_SYNTAX, "Expected SWAP VARIABLE, VARIABLE");
/*
* Refused across types, which BASIC 7.0 also does. A# and B$ hold different
* things and swapping them would leave two variables whose names no longer
* describe their contents -- and the type suffix is the only type
* declaration this language has.
*/
FAIL_NONZERO_RETURN(errctx, (first->leaftype != second->leaftype), AKBASIC_ERR_TYPE,
"SWAP needs two variables of the same type");
PASS(errctx, akbasic_environment_get(obj->environment, first->identifier, &a));
PASS(errctx, akbasic_environment_get(obj->environment, second->identifier, &b));
FAIL_ZERO_RETURN(errctx, (a != NULL && b != NULL), AKBASIC_ERR_UNDEFINED,
"SWAP could not reach %s or %s", first->identifier, second->identifier);
if ( a == b ) {
/* SWAP A#, A# is legal and does nothing, rather than corrupting itself. */
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
/*
* The whole record except the name, so an array swaps its dimensions and its
* storage pointer along with its contents -- SWAP is documented as exchanging
* the *variables*, not their scalar values, and on a C128 it is O(1) for
* exactly this reason. The names stay put: they are what the symbol table
* points at.
*/
memcpy(namea, a->name, sizeof(namea));
memcpy(nameb, b->name, sizeof(nameb));
memcpy(&swap, a, sizeof(swap));
memcpy(a, b, sizeof(*a));
memcpy(b, &swap, sizeof(*b));
memcpy(a->name, namea, sizeof(a->name));
memcpy(b->name, nameb, sizeof(b->name));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
/* ---------------------------------------------------------------- HELP --- */
akerr_ErrorContext *akbasic_cmd_help(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
char line[AKBASIC_MAX_LINE_LENGTH * 2];
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in HELP");
/*
* A C128 re-lists the line the last error happened on and highlights the
* part it choked on. The line is reproduced here; the highlight is not,
* because the error is reported by the verb that raised it rather than by
* something holding a token offset, so there is nothing that knows which
* part to mark. Printing the line without pretending to know more is the
* honest half of the verb.
*/
if ( obj->errorline == 0 || obj->source[obj->errorline].code[0] == '\0' ) {
PASS(errctx, akbasic_runtime_println(obj, "NO ERROR TO HELP WITH"));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
snprintf(line, sizeof(line), "%" PRId64 " %s",
obj->errorline, obj->source[obj->errorline].code);
PASS(errctx, akbasic_runtime_println(obj, line));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}