Add backtraces to errors
This commit is contained in:
parent
3cc2a83226
commit
bd19f689b9
7 changed files with 184 additions and 114 deletions
19
src/apfl.h
19
src/apfl.h
|
|
@ -619,12 +619,9 @@ typedef size_t apfl_slotidx;
|
|||
|
||||
enum apfl_result {
|
||||
APFL_RESULT_OK, // Evaluation succeeded, value is on the stack
|
||||
|
||||
// TODO: No further details are yet provided on (fatal) error. Not quite
|
||||
// sure what to return / how errors/exceptions should work. Maybe a
|
||||
// value + a backtrace?
|
||||
APFL_RESULT_ERR,
|
||||
APFL_RESULT_ERR_ALLOC,
|
||||
APFL_RESULT_ERR, // Evaluation failed, an error is on the stack
|
||||
APFL_RESULT_ERRERR, // Evaluation failed and an error occurred during error handling, no error on the stack.
|
||||
APFL_RESULT_ERR_ALLOC, // Evaluation failed due to memory allocation error, no error on the stack.
|
||||
};
|
||||
|
||||
struct apfl_config {
|
||||
|
|
@ -636,7 +633,7 @@ apfl_ctx apfl_ctx_new(struct apfl_config);
|
|||
|
||||
void apfl_ctx_destroy(apfl_ctx);
|
||||
|
||||
typedef void (*apfl_panic_callback)(apfl_ctx, bool with_error_on_stack, void *);
|
||||
typedef void (*apfl_panic_callback)(apfl_ctx, void *, enum apfl_result);
|
||||
|
||||
void apfl_ctx_set_panic_callback(apfl_ctx, apfl_panic_callback, void *);
|
||||
|
||||
|
|
@ -645,8 +642,12 @@ typedef struct apfl_iterative_runner_data *apfl_iterative_runner;
|
|||
apfl_iterative_runner apfl_iterative_runner_new(apfl_ctx, struct apfl_source_reader);
|
||||
bool apfl_iterative_runner_next(apfl_iterative_runner);
|
||||
enum apfl_result apfl_iterative_runner_get_result(apfl_iterative_runner);
|
||||
bool apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner);
|
||||
bool apfl_iterative_runner_stopped_because_of_error(apfl_iterative_runner);
|
||||
bool apfl_iterative_runner_run_repl(
|
||||
apfl_iterative_runner,
|
||||
struct apfl_format_writer w_out,
|
||||
struct apfl_format_writer w_err
|
||||
);
|
||||
void apfl_iterative_runner_destroy(apfl_iterative_runner);
|
||||
|
||||
typedef void (*apfl_cfunc)(apfl_ctx);
|
||||
|
|
@ -722,7 +723,7 @@ void apfl_push_userdata(apfl_ctx, void *);
|
|||
void *apfl_get_userdata(apfl_ctx, apfl_stackidx);
|
||||
|
||||
void apfl_call(apfl_ctx, apfl_stackidx func, apfl_stackidx args);
|
||||
enum apfl_result apfl_call_protected(apfl_ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack);
|
||||
enum apfl_result apfl_call_protected(apfl_ctx, apfl_stackidx func, apfl_stackidx args);
|
||||
|
||||
enum apfl_call_stack_entry_type {
|
||||
APFL_CSE_FUNCTION,
|
||||
|
|
|
|||
|
|
@ -19,22 +19,33 @@ static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx);
|
|||
static struct apfl_string *new_copied_string(struct gc *, struct apfl_string_view);
|
||||
|
||||
APFL_NORETURN static void
|
||||
panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result)
|
||||
panic(apfl_ctx ctx, enum apfl_result result)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)result;
|
||||
(void)with_error_on_stack;
|
||||
fprintf(stderr, "panic!\n");
|
||||
// TODO: more details
|
||||
abort();
|
||||
}
|
||||
|
||||
struct protected_errcallback_data {
|
||||
void *opaque_outer;
|
||||
void (*errcallback)(apfl_ctx, void *);
|
||||
};
|
||||
|
||||
void
|
||||
protected_errcallback(apfl_ctx ctx, void *opaque)
|
||||
{
|
||||
struct protected_errcallback_data *data = opaque;
|
||||
data->errcallback(ctx, data->opaque_outer);
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
apfl_do_protected(
|
||||
apfl_ctx ctx,
|
||||
void (*callback)(apfl_ctx, void *),
|
||||
void *opaque,
|
||||
bool *with_error_on_stack
|
||||
void (*errcallback)(apfl_ctx, void *)
|
||||
) {
|
||||
struct error_handler *prev_handler = ctx->error_handler;
|
||||
|
||||
|
|
@ -52,17 +63,45 @@ apfl_do_protected(
|
|||
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
|
||||
assert(result != APFL_RESULT_OK);
|
||||
|
||||
struct apfl_value err;
|
||||
if ((*with_error_on_stack = handler.with_error_on_stack)) {
|
||||
if (!apfl_stack_pop(ctx, &err, -1)) {
|
||||
*with_error_on_stack = false;
|
||||
// TODO: Indicate error during error handling
|
||||
bool with_error_on_stack = APFL_RESULT_ERR;
|
||||
|
||||
if (with_error_on_stack && errcallback != NULL) {
|
||||
struct protected_errcallback_data data = {
|
||||
.opaque_outer = opaque,
|
||||
.errcallback = errcallback,
|
||||
};
|
||||
switch (apfl_do_protected(ctx, protected_errcallback, &data, NULL)) {
|
||||
case APFL_RESULT_OK:
|
||||
break;
|
||||
case APFL_RESULT_ERR:
|
||||
result = APFL_RESULT_ERRERR;
|
||||
with_error_on_stack = false;
|
||||
break;
|
||||
case APFL_RESULT_ERRERR:
|
||||
result = APFL_RESULT_ERRERR;
|
||||
with_error_on_stack = false;
|
||||
break;
|
||||
case APFL_RESULT_ERR_ALLOC:
|
||||
result = APFL_RESULT_ERR_ALLOC;
|
||||
with_error_on_stack = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
||||
assert(callstack_len <= ctx->call_stack.cap);
|
||||
struct apfl_value err;
|
||||
if (with_error_on_stack && !apfl_stack_pop(ctx, &err, -1)) {
|
||||
result = APFL_RESULT_ERRERR;
|
||||
with_error_on_stack = false;
|
||||
}
|
||||
|
||||
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
||||
tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
||||
if (with_error_on_stack && !apfl_value_add_as_tmproot(&ctx->gc, err)) {
|
||||
result = APFL_RESULT_ERRERR;
|
||||
with_error_on_stack = false;
|
||||
}
|
||||
|
||||
assert(callstack_len <= ctx->call_stack.cap);
|
||||
for (size_t i = callstack_len; i < ctx->call_stack.len; i++) {
|
||||
apfl_call_stack_entry_deinit(ctx->gc.allocator, &ctx->call_stack.items[i]);
|
||||
}
|
||||
|
|
@ -79,12 +118,12 @@ apfl_do_protected(
|
|||
)
|
||||
);
|
||||
|
||||
if (*with_error_on_stack) {
|
||||
if (!apfl_stack_push(ctx, err)) {
|
||||
*with_error_on_stack = false;
|
||||
// TODO: Indicate error during error handling
|
||||
}
|
||||
if (with_error_on_stack && !apfl_stack_push(ctx, err)) {
|
||||
result = APFL_RESULT_ERRERR;
|
||||
with_error_on_stack = false;
|
||||
}
|
||||
|
||||
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
||||
}
|
||||
|
||||
ctx->error_handler = prev_handler;
|
||||
|
|
@ -105,49 +144,53 @@ call_protected_cb(apfl_ctx ctx, void *opaque)
|
|||
}
|
||||
|
||||
enum apfl_result
|
||||
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack)
|
||||
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args)
|
||||
{
|
||||
struct call_protected_data data = {
|
||||
.func = func,
|
||||
.args = args,
|
||||
};
|
||||
return apfl_do_protected(ctx, call_protected_cb, &data, with_error_on_stack);
|
||||
return apfl_do_protected(ctx, call_protected_cb, &data, NULL);
|
||||
}
|
||||
|
||||
APFL_NORETURN static void
|
||||
raise_error(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result type)
|
||||
raise_error(apfl_ctx ctx, enum apfl_result type)
|
||||
{
|
||||
assert(type != APFL_RESULT_OK);
|
||||
|
||||
if (ctx->error_handler != NULL) {
|
||||
ctx->error_handler->with_error_on_stack = with_error_on_stack;
|
||||
longjmp(ctx->error_handler->jump, (int)type + RESULT_OFF_FOR_LONGJMP);
|
||||
}
|
||||
|
||||
if (ctx->panic_callback != NULL) {
|
||||
ctx->panic_callback(ctx, with_error_on_stack, ctx->panic_callback_data);
|
||||
ctx->panic_callback(ctx, ctx->panic_callback_data, type);
|
||||
}
|
||||
|
||||
panic(ctx, with_error_on_stack, type);
|
||||
panic(ctx, type);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_error(apfl_ctx ctx, apfl_stackidx idx)
|
||||
{
|
||||
bool ok = current_stack_move_to_top(ctx, idx);
|
||||
raise_error(ctx, ok, APFL_RESULT_ERR);
|
||||
if (!current_stack_move_to_top(ctx, idx)) {
|
||||
raise_error(ctx, APFL_RESULT_ERRERR);
|
||||
}
|
||||
raise_error(ctx, APFL_RESULT_ERR);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_const_error(apfl_ctx ctx, const char *message)
|
||||
{
|
||||
raise_error(ctx, try_push_const_string(ctx, message), APFL_RESULT_ERR);
|
||||
if (!try_push_const_string(ctx, message)) {
|
||||
raise_error(ctx, APFL_RESULT_ERRERR);
|
||||
}
|
||||
raise_error(ctx, APFL_RESULT_ERR);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_alloc_error(apfl_ctx ctx)
|
||||
{
|
||||
raise_error(ctx, false, APFL_RESULT_ERR_ALLOC);
|
||||
raise_error(ctx, APFL_RESULT_ERR_ALLOC);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
|
|
|
|||
|
|
@ -129,7 +129,6 @@ struct call_stack {
|
|||
|
||||
struct error_handler {
|
||||
jmp_buf jump;
|
||||
bool with_error_on_stack;
|
||||
};
|
||||
|
||||
struct iterative_runners_list {
|
||||
|
|
@ -202,7 +201,7 @@ enum apfl_result apfl_do_protected(
|
|||
apfl_ctx ctx,
|
||||
void (*callback)(apfl_ctx, void *),
|
||||
void *opaque,
|
||||
bool *with_error_on_stack
|
||||
void (*errcallback)(apfl_ctx, void *)
|
||||
);
|
||||
|
||||
bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner);
|
||||
|
|
|
|||
104
src/eval.c
104
src/eval.c
|
|
@ -1353,7 +1353,6 @@ struct apfl_iterative_runner_data {
|
|||
apfl_tokenizer_ptr tokenizer;
|
||||
apfl_parser_ptr parser;
|
||||
enum apfl_result result;
|
||||
bool with_error_on_stack;
|
||||
enum interative_runner_state state;
|
||||
struct scope *scope;
|
||||
};
|
||||
|
|
@ -1494,6 +1493,54 @@ iterative_runner_next_protected(apfl_ctx ctx, void *opaque)
|
|||
assert(false);
|
||||
}
|
||||
|
||||
#define DECORATE_TRY_FMT(ctx, x) do { if (!(x)) { apfl_raise_alloc_error(ctx); } } while (0)
|
||||
|
||||
static void
|
||||
iterative_runner_decorate_error(apfl_ctx ctx, void *opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
|
||||
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
|
||||
struct apfl_format_writer w = apfl_format_string_writer(&sb);
|
||||
|
||||
apfl_tostring(ctx, -1);
|
||||
|
||||
if (!(
|
||||
apfl_format_put_string(w, apfl_get_string(ctx, -1))
|
||||
&& apfl_format_put_string(w, "\n\nBacktrace:")
|
||||
)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
size_t depth = apfl_call_stack_depth(ctx);
|
||||
for (size_t i = 0; i < depth; i++) {
|
||||
if (!(
|
||||
apfl_format_put_string(w, "\n")
|
||||
&& apfl_format_put_string(w, "#")
|
||||
&& apfl_format_put_int(w, (int)i+1)
|
||||
&& apfl_format_put_string(w, ": ")
|
||||
&& apfl_call_stack_entry_info_format(
|
||||
w,
|
||||
apfl_call_stack_inspect(ctx, i)
|
||||
)
|
||||
)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
struct apfl_string string = apfl_string_builder_move_string(&sb);
|
||||
apfl_string_builder_deinit(&sb);
|
||||
|
||||
if (!apfl_move_string_onto_stack(ctx, string)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
return;
|
||||
|
||||
fail:
|
||||
apfl_string_builder_deinit(&sb);
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_iterative_runner_next(apfl_iterative_runner runner)
|
||||
{
|
||||
|
|
@ -1503,7 +1550,12 @@ apfl_iterative_runner_next(apfl_iterative_runner runner)
|
|||
|
||||
apfl_stack_clear(runner->ctx);
|
||||
|
||||
runner->result = apfl_do_protected(runner->ctx, iterative_runner_next_protected, runner, &runner->with_error_on_stack);
|
||||
runner->result = apfl_do_protected(
|
||||
runner->ctx,
|
||||
iterative_runner_next_protected,
|
||||
runner,
|
||||
iterative_runner_decorate_error
|
||||
);
|
||||
|
||||
return runner->state == IRUNNER_OK;
|
||||
}
|
||||
|
|
@ -1514,18 +1566,54 @@ apfl_iterative_runner_get_result(apfl_iterative_runner runner)
|
|||
return runner->result;
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner runner)
|
||||
{
|
||||
return runner->with_error_on_stack;
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_iterative_runner_stopped_because_of_error(apfl_iterative_runner runner)
|
||||
{
|
||||
return runner->state == IRUNNER_ERR;
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_iterative_runner_run_repl(
|
||||
apfl_iterative_runner runner,
|
||||
struct apfl_format_writer w_out,
|
||||
struct apfl_format_writer w_err
|
||||
) {
|
||||
apfl_ctx ctx = runner->ctx;
|
||||
|
||||
while (apfl_iterative_runner_next(runner)) {
|
||||
switch (apfl_iterative_runner_get_result(runner)) {
|
||||
case APFL_RESULT_OK :
|
||||
if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) {
|
||||
apfl_drop(ctx, -1);
|
||||
} else {
|
||||
FMT_TRY(apfl_debug_print_val(ctx, -1, w_out));
|
||||
}
|
||||
break;
|
||||
case APFL_RESULT_ERR:
|
||||
FMT_TRY(apfl_format_put_string(w_err, "Error occurred during evaluation:\n"));
|
||||
if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) {
|
||||
FMT_TRY(apfl_format_put_string(w_err, apfl_get_string(ctx, -1)));
|
||||
} else {
|
||||
FMT_TRY(apfl_debug_print_val(ctx, -1, w_err));
|
||||
}
|
||||
FMT_TRY(apfl_format_put_char(w_err, '\n'));
|
||||
break;
|
||||
case APFL_RESULT_ERRERR:
|
||||
FMT_TRY(apfl_format_put_string(w_err, "Error occurred during error handling.\n"));
|
||||
break;
|
||||
case APFL_RESULT_ERR_ALLOC:
|
||||
FMT_TRY(apfl_format_put_string(w_err, "Fatal: Could not allocate memory.\n"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (apfl_iterative_runner_stopped_because_of_error(runner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
apfl_iterative_runner_destroy(apfl_iterative_runner runner)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ parse_test(const struct apfl_string str, struct test_parts *parts)
|
|||
static void
|
||||
dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner)
|
||||
{
|
||||
if (apfl_iterative_runner_has_error_on_stack(runner)) {
|
||||
if (apfl_iterative_runner_get_result(runner) == APFL_RESULT_ERR) {
|
||||
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr)));
|
||||
}
|
||||
}
|
||||
|
|
@ -175,6 +175,9 @@ runtest(const char *filename)
|
|||
fprintf(stderr, "Error occurred during evaluation.\n");
|
||||
dump_stack_error(ctx, runner);
|
||||
return T_ERR;
|
||||
case APFL_RESULT_ERRERR:
|
||||
fprintf(stderr, "Error while handling error.\n");
|
||||
return T_ERR;
|
||||
case APFL_RESULT_ERR_ALLOC:
|
||||
fprintf(stderr, "Fatal: Could not allocate memory.\n");
|
||||
return T_FATAL;
|
||||
|
|
|
|||
38
src/main.c
38
src/main.c
|
|
@ -138,22 +138,15 @@ exit:
|
|||
return rv;
|
||||
}
|
||||
|
||||
static void
|
||||
dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner)
|
||||
{
|
||||
if (apfl_iterative_runner_has_error_on_stack(runner)) {
|
||||
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr)));
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
repl_eval(void)
|
||||
{
|
||||
int rv = 0;
|
||||
struct apfl_format_writer w_out = apfl_format_file_writer(stdout);
|
||||
struct apfl_format_writer w_err = apfl_format_file_writer(stderr);
|
||||
|
||||
apfl_ctx ctx = apfl_ctx_new((struct apfl_config) {
|
||||
.allocator = apfl_allocator_default(),
|
||||
.output_writer = apfl_format_file_writer(stdout),
|
||||
.output_writer = w_out,
|
||||
});
|
||||
if (ctx == NULL) {
|
||||
fprintf(stderr, "Failed to init the context\n");
|
||||
|
|
@ -167,31 +160,8 @@ repl_eval(void)
|
|||
return 1;
|
||||
}
|
||||
|
||||
while (apfl_iterative_runner_next(runner)) {
|
||||
switch (apfl_iterative_runner_get_result(runner)) {
|
||||
case APFL_RESULT_OK :
|
||||
if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) {
|
||||
apfl_drop(ctx, -1);
|
||||
} else {
|
||||
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stdout)));
|
||||
}
|
||||
break;
|
||||
case APFL_RESULT_ERR:
|
||||
dump_stack_error(ctx, runner);
|
||||
fprintf(stderr, "Error occurred during evaluation.\n");
|
||||
break;
|
||||
case APFL_RESULT_ERR_ALLOC:
|
||||
fprintf(stderr, "Fatal: Could not allocate memory.\n");
|
||||
rv = 1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
int rv = apfl_iterative_runner_run_repl(runner, w_out, w_err) ? 0 : 1;
|
||||
|
||||
if (apfl_iterative_runner_stopped_because_of_error(runner)) {
|
||||
rv = 1;
|
||||
}
|
||||
|
||||
exit:
|
||||
apfl_iterative_runner_destroy(runner);
|
||||
apfl_ctx_destroy(ctx);
|
||||
return rv;
|
||||
|
|
|
|||
|
|
@ -62,19 +62,9 @@ playground_source_reader_cb(void *context, char *buf, size_t *len, bool need)
|
|||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner, struct apfl_format_writer w_err)
|
||||
{
|
||||
if (apfl_iterative_runner_has_error_on_stack(runner)) {
|
||||
assert(apfl_debug_print_val(ctx, -1, w_err));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
int rv = 0;
|
||||
|
||||
struct apfl_format_writer w_ok = {.write = web_fmt_write, .opaque = (void *)0};
|
||||
struct apfl_format_writer w_err = {.write = web_fmt_write, .opaque = (void *)1};
|
||||
|
||||
|
|
@ -104,32 +94,8 @@ main(void)
|
|||
return 1;
|
||||
}
|
||||
|
||||
while (apfl_iterative_runner_next(runner)) {
|
||||
switch (apfl_iterative_runner_get_result(runner)) {
|
||||
case APFL_RESULT_OK :
|
||||
if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) {
|
||||
apfl_drop(ctx, -1);
|
||||
} else {
|
||||
assert(apfl_debug_print_val(ctx, -1, w_ok));
|
||||
}
|
||||
break;
|
||||
case APFL_RESULT_ERR:
|
||||
dump_stack_error(ctx, runner, w_err);
|
||||
assert(apfl_format_put_string(w_err, "Error occurred during evaluation.\n"));
|
||||
break;
|
||||
case APFL_RESULT_ERR_ALLOC:
|
||||
assert(apfl_format_put_string(w_err, "Fatal: Could not allocate memory.\n"));
|
||||
rv = 1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
int rv = apfl_iterative_runner_run_repl(runner, w_ok, w_err) ? 0 : 1;
|
||||
|
||||
if (apfl_iterative_runner_stopped_because_of_error(runner)) {
|
||||
assert(apfl_format_put_string(w_err, "Runner stopped due to error.\n"));
|
||||
rv = 1;
|
||||
}
|
||||
|
||||
exit:
|
||||
free(source_reader_data.str);
|
||||
apfl_iterative_runner_destroy(runner);
|
||||
apfl_ctx_destroy(ctx);
|
||||
|
|
|
|||
Loading…
Reference in a new issue