/** * @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 #include #include #include #include #include #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; int cmp = 0; bool isnext = false; (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 && target->leaftype == AKBASIC_LEAF_COMMAND ) { PASS(errctx, aksl_strcmp(target->identifier, "NEXT", &cmp)); isnext = (cmp == 0); } 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 ( isnext ) { /* 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); PASS(errctx, aksl_strcpy(out->stringval, sizeof(out->stringval), (name != NULL ? name : "Unknown Error"))); *dest = out; SUCCEED_RETURN(errctx); }