-
Notifications
You must be signed in to change notification settings - Fork 126
Structured exception handling
The retro ON ERROR GOTO
error handling works fine with line number based BASIC, but it's bad for a language with modern syntax. Error handling in MY-BASIC is simple, it just prompts an error message, then terminates execution flow. However it's possible to fork an isolated execution environment, with which code runs as if in a sub environment. A forked environment shares the same parsed AST, scope chain, GC context, etc. but separates some states such as error state. So we can check execution state without breaking the main execution flow when an error occurred.
The mb_fork
function is used to fork an environment from an exist one.
To implement a TRY
statement for structured exception handling, add a function first:
#define _mb_check_mark(__expr, __result, __exit) do { __result = (__expr); if(__result != MB_FUNC_OK) goto __exit; } while(0)
static int _try_catch_finally(struct mb_interpreter_t* s, void** l) {
int result = MB_FUNC_OK;
struct mb_interpreter_t* forked = 0;
mb_value_t try_routine;
mb_value_t catch_routine;
mb_value_t finally_routine;
mb_value_t ret;
mb_assert(s && l);
mb_make_nil(try_routine);
mb_make_nil(catch_routine);
mb_make_nil(finally_routine);
mb_make_nil(ret);
/* Get arguments */
_mb_check_mark(mb_attempt_open_bracket(s, l), result, _exit);
_mb_check_mark(mb_pop_value(s, l, &try_routine), result, _exit);
if(mb_has_arg(s, l)) {
_mb_check_mark(mb_pop_value(s, l, &catch_routine), result, _exit);
}
if(mb_has_arg(s, l)) {
_mb_check_mark(mb_pop_value(s, l, &finally_routine), result, _exit);
}
_mb_check_mark(mb_attempt_close_bracket(s, l), result, _exit);
do {
void* ast = *l;
mb_value_t args[1];
mb_make_nil(args[0]);
mb_error_e err = SE_NO_ERR;
int ecode = MB_FUNC_OK;
/* Fork an isolated environment */
mb_fork(&forked, s);
/* Evaluate try routine with forked */
ecode = mb_eval_routine(forked, &ast, try_routine, args, 0, &ret);
/* Evaluate catch routine if error occurred */
err = mb_get_last_error(forked);
if(err != SE_NO_ERR) {
const char* errmsg = mb_get_error_desc(err);
if (catch_routine.type == MB_DT_ROUTINE) {
mb_value_t eargs[1];
mb_make_string(eargs[0], (char*)errmsg);
err = SE_NO_ERR;
mb_eval_routine(s, l, catch_routine, eargs, 1, 0);
}
}
/* Evaluate finally routine */
if(finally_routine.type == MB_DT_ROUTINE) {
mb_eval_routine(s, l, finally_routine, args, 0, 0);
}
/* Raise the error if it's not caught */
if(err != SE_NO_ERR) {
result = mb_raise_error(s, l, err, ecode);
}
/* Close forked */
mb_close_forked(&forked);
} while(0);
_exit:
/* Clean up routines */
if(try_routine.type == MB_DT_ROUTINE) {
mb_check(mb_unref_value(s, l, try_routine));
}
if(catch_routine.type == MB_DT_ROUTINE) {
mb_check(mb_unref_value(s, l, catch_routine));
}
if(finally_routine.type == MB_DT_ROUTINE) {
mb_check(mb_unref_value(s, l, finally_routine));
}
/* Return the value of the returned one from try routine */
mb_check(mb_push_value(s, l, ret));
return result;
}
Then register it:
mb_register_func(bas, "TRY", _try_catch_finally);
The TRY
statement accepts three arguments. And works as:
- It invokes the first "try" invokable argument
- Invokes the second "catch" routine by passing the error text, if any error occurred in the "try" routine
- The third "finally" routine is always invoked whether error occurred or not
- A
TRY
statement returns the value of the returned one from "try" routine
For example:
ret = try(
lambda () (
print "Try.";
return 42
),
lambda (_) (
print "Catch: ", _, ".";
),
lambda () (
print "Finally.";
)
)
print ret;
The "catch" and "finally" routines are optional:
try(
lambda () (
print "Try.";
)
)
You may test it with error:
try(
lambda () (
print "Try.";
raise(0)
),
lambda (_) (
print "Catch: ", _, ".";
),
lambda () (
print "Finally.";
)
)
It's also possible to use the mb_set_error_handler
function to redirect error handler of a forked environment, otherwise it uses the same handler of the base environment.
- Principles
- Coding
- Data types
- Standalone shell
- Integration
- Customization
- More scripting API
- FAQ