190 lines
7.6 KiB
C
190 lines
7.6 KiB
C
|
|
/**
|
||
|
|
* @file runtime_trap.c
|
||
|
|
* @brief The group C verbs: TRAP, RESUME, and the ERR() message lookup.
|
||
|
|
*
|
||
|
|
* Error trapping, built on the interrupt machinery COLLISION already uses:
|
||
|
|
* #AKBASIC_INTERRUPT_ERROR was declared with it and armed by nothing until now.
|
||
|
|
* A trapped error is a GOSUB the program did not write, entered at a line
|
||
|
|
* boundary, and `RESUME` is its `RETURN`.
|
||
|
|
*
|
||
|
|
* **`ER` and `EL` are ordinary global variables here, spelled `ER#` and `EL#`.**
|
||
|
|
* A C128 exposes them as bare reserved names, which this dialect cannot do: an
|
||
|
|
* identifier carries its type in a suffix and a bare name is a *label*
|
||
|
|
* (`LABEL DONE` / `GOTO DONE`). Writing them into the global scope costs nothing,
|
||
|
|
* needs no new syntax, and `PRINT ER#` reads the way the rest of the language
|
||
|
|
* does. Recorded in TODO.md section 5.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <inttypes.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akbasic/args.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 )
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_trap_set_error_variables(akbasic_Runtime *obj, int status, int64_t line)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_Variable *variable = NULL;
|
||
|
|
int64_t zerosubscript[1] = { 0 };
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL runtime in set_error_variables");
|
||
|
|
/*
|
||
|
|
* The *global* scope, not the active one. A trapped error is reported from
|
||
|
|
* wherever it happened -- inside a GOSUB body, inside a loop -- and a
|
||
|
|
* handler that reads ER# has usually been entered through a scope of its
|
||
|
|
* own. akbasic_runtime_global() walks to the root, which is the only place
|
||
|
|
* both can see.
|
||
|
|
*/
|
||
|
|
PASS(errctx, akbasic_runtime_global(obj, "ER#", &variable));
|
||
|
|
PASS(errctx, akbasic_variable_set_integer(variable, (int64_t)status, zerosubscript, 1));
|
||
|
|
PASS(errctx, akbasic_runtime_global(obj, "EL#", &variable));
|
||
|
|
PASS(errctx, akbasic_variable_set_integer(variable, line, zerosubscript, 1));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------ TRAP -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_trap(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_ASTLeaf *target = NULL;
|
||
|
|
akbasic_Value *value = NULL;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in TRAP");
|
||
|
|
|
||
|
|
target = (expr != NULL ? expr->right : NULL);
|
||
|
|
if ( target == NULL ) {
|
||
|
|
/* Bare TRAP disarms, which is how a C128 turns trapping back off. */
|
||
|
|
PASS(errctx, akbasic_runtime_disarm_interrupt(obj, AKBASIC_INTERRUPT_ERROR));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* A label is taken by name and resolved when the trap fires, exactly as
|
||
|
|
* COLLISION's handler is: an error handler sits on a line normal flow does
|
||
|
|
* not fall into.
|
||
|
|
*/
|
||
|
|
if ( target->leaftype == AKBASIC_LEAF_IDENTIFIER &&
|
||
|
|
akbasic_leaf_first_subscript(target) == NULL ) {
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(obj, AKBASIC_INTERRUPT_ERROR,
|
||
|
|
0, target->identifier));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, target, &value));
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
|
||
|
|
"TRAP expected a line number or a label");
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(obj, AKBASIC_INTERRUPT_ERROR,
|
||
|
|
value->intval, NULL));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------- RESUME -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_resume(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_ASTLeaf *target = NULL;
|
||
|
|
akbasic_Value *value = NULL;
|
||
|
|
akbasic_Variable *variable = NULL;
|
||
|
|
int64_t zerosubscript[1] = { 0 };
|
||
|
|
int64_t resumeline = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in RESUME");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj->handlerenv != NULL), AKBASIC_ERR_STATE,
|
||
|
|
"RESUME outside the context of a TRAP handler");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj->environment == obj->handlerenv), AKBASIC_ERR_STATE,
|
||
|
|
"RESUME must return from the handler itself, not from a GOSUB inside it");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj->environment->parent != NULL), AKBASIC_ERR_ENVIRONMENT,
|
||
|
|
"RESUME from an orphaned environment");
|
||
|
|
|
||
|
|
/* Where the error happened, which EL# is holding. */
|
||
|
|
PASS(errctx, akbasic_runtime_global(obj, "EL#", &variable));
|
||
|
|
PASS(errctx, akbasic_variable_get_subscript(variable, zerosubscript, 1, &value));
|
||
|
|
resumeline = value->intval;
|
||
|
|
|
||
|
|
target = (expr != NULL ? expr->right : NULL);
|
||
|
|
if ( target == NULL ) {
|
||
|
|
/*
|
||
|
|
* Bare RESUME retries the line that failed. That is a loop unless the
|
||
|
|
* handler fixed whatever was wrong, which is the program's business and
|
||
|
|
* is exactly what the verb is for.
|
||
|
|
*/
|
||
|
|
obj->environment->parent->nextline = resumeline;
|
||
|
|
} else if ( target->leaftype == AKBASIC_LEAF_COMMAND &&
|
||
|
|
strcmp(target->identifier, "NEXT") == 0 ) {
|
||
|
|
/* RESUME NEXT carries on at the line after the one that failed. */
|
||
|
|
obj->environment->parent->nextline = resumeline + 1;
|
||
|
|
} else {
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, target, &value));
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_INTEGER),
|
||
|
|
AKBASIC_ERR_TYPE, "RESUME expected NEXT, a line number or a label");
|
||
|
|
obj->environment->parent->nextline = value->intval;
|
||
|
|
}
|
||
|
|
|
||
|
|
obj->handlerenv = NULL;
|
||
|
|
PASS(errctx, akbasic_runtime_prev_environment(obj));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------- ERR -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_fn_err(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_ASTLeaf *arg = NULL;
|
||
|
|
akbasic_Value *value = NULL;
|
||
|
|
akbasic_Value *out = NULL;
|
||
|
|
const char *name = NULL;
|
||
|
|
int64_t status = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && expr != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in ERR");
|
||
|
|
arg = akbasic_leaf_first_argument(expr);
|
||
|
|
FAIL_ZERO_RETURN(errctx, (arg != NULL), AKBASIC_ERR_SYNTAX, "ERR expected an error number");
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||
|
|
"ERR expected a number");
|
||
|
|
status = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
||
|
|
? (int64_t)value->floatval : value->intval;
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
|
||
|
|
PASS(errctx, akbasic_value_zero(out));
|
||
|
|
out->valuetype = AKBASIC_TYPE_STRING;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The name libakerror registered for the code, which is where every status
|
||
|
|
* in this process is named -- akbasic's own band, libakgl's, and the errno
|
||
|
|
* values underneath. It answers for an unregistered code too, with "Unknown
|
||
|
|
* Error", so there is nothing to add here; the NULL guard is for a libakerror
|
||
|
|
* that ever decides otherwise.
|
||
|
|
*/
|
||
|
|
name = akerr_name_for_status((int)status, NULL);
|
||
|
|
snprintf(out->stringval, sizeof(out->stringval), "%s",
|
||
|
|
(name != NULL ? name : "Unknown Error"));
|
||
|
|
*dest = out;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|