diff --git a/src/apfl.h b/src/apfl.h index ddec212..ad4d541 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -724,6 +724,28 @@ 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_call_stack_entry_type { + APFL_CSE_FUNCTION, + APFL_CSE_CFUNCTION, + APFL_CSE_MATCHER, + APFL_CSE_FUNCTION_DISPATCH, +}; + +struct apfl_call_stack_entry_info { + enum apfl_call_stack_entry_type type; + bool toplevel; // Only set for type==APFL_CSE_FUNCTION. + size_t subfunction_index; // Only set for type==APFL_CSE_FUNCTION && !toplevel + struct apfl_string_view name; + int line_current; // only set for (type==APFL_CSE_FUNCTION && !toplevel) || type == APFL_CSE_FUNCTION_DISPATCH + int line_defined; // only set for type==APFL_CSE_FUNCTION +}; + +// Get the depth of the current call stack +size_t apfl_call_stack_depth(apfl_ctx); +// Get information about the n-th call stack entry, where n=0 is the current entry. +struct apfl_call_stack_entry_info apfl_call_stack_inspect(apfl_ctx, size_t n); +bool apfl_call_stack_entry_info_format(struct apfl_format_writer, struct apfl_call_stack_entry_info); + bool apfl_debug_print_val(apfl_ctx, apfl_stackidx, struct apfl_format_writer); // Raise an error with a value from the stack as the message. @@ -755,6 +777,7 @@ struct apfl_messages { const char *value_doesnt_match; const char *invalid_matcher_state; const char *no_matching_subfunction; + const char *invalid_call_stack_index; }; extern const struct apfl_messages apfl_messages; diff --git a/src/context.c b/src/context.c index 38b1ce8..dc84668 100644 --- a/src/context.c +++ b/src/context.c @@ -660,16 +660,16 @@ gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, vo stack_traverse(cse.stack, visitor, opaque); switch (cse.type) { - case CSE_FUNCTION: + case APFL_CSE_FUNCTION: visit_func_cse(cse.func, visitor, opaque); break; - case CSE_CFUNCTION: + case APFL_CSE_CFUNCTION: visit_cfunc_cse(cse.cfunc, visitor, opaque); break; - case CSE_MATCHER: + case APFL_CSE_MATCHER: visit_matcher_cse(cse.matcher, visitor, opaque); break; - case CSE_FUNCTION_DISPATCH: + case APFL_CSE_FUNCTION_DISPATCH: visit_func_dispatch_cse(cse.func_dispatch, visitor, opaque); break; } @@ -735,13 +735,13 @@ apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_ deinit_stack(allocator, &entry->stack); switch (entry->type) { - case CSE_FUNCTION: + case APFL_CSE_FUNCTION: func_call_stack_entry_deinit(allocator, &entry->func); break; - case CSE_CFUNCTION: - case CSE_FUNCTION_DISPATCH: + case APFL_CSE_CFUNCTION: + case APFL_CSE_FUNCTION_DISPATCH: break; - case CSE_MATCHER: + case APFL_CSE_MATCHER: apfl_matcher_call_stack_entry_deinit(allocator, &entry->matcher); break; } @@ -1621,7 +1621,7 @@ must_get_cfunc_self(apfl_ctx ctx) raise_no_cfunc(ctx); } - if (entry->type != CSE_CFUNCTION) { + if (entry->type != APFL_CSE_CFUNCTION) { raise_no_cfunc(ctx); } @@ -1774,3 +1774,121 @@ apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaq apfl_value_visit_gc_object(matcher->values[i], visitor, opaque); } } + +size_t +apfl_call_stack_depth(apfl_ctx ctx) +{ + return ctx->call_stack.len; +} + +struct apfl_call_stack_entry_info +apfl_call_stack_inspect(apfl_ctx ctx, size_t n) +{ + if (n >= ctx->call_stack.len) { + apfl_raise_const_error(ctx, apfl_messages.invalid_call_stack_index); + } + + struct call_stack_entry *cse = &ctx->call_stack.items[ctx->call_stack.len - n - 1]; + + struct apfl_call_stack_entry_info info = { + .type = cse->type, + .name = (struct apfl_string_view) { .len = 0, .bytes = NULL, }, + .toplevel = false, + }; + + switch (cse->type) { + case APFL_CSE_FUNCTION: + info.line_current = cse->func.execution_line; + if (cse->func.function == NULL) { + info.toplevel = true; + } else { + info.line_defined = cse->func.instructions->line; + info.subfunction_index = cse->func.subfunction_index; + + struct apfl_string_view sv; + if (apfl_func_get_name(cse->func.function, &sv)) { + info.name = sv; + } + } + break; + case APFL_CSE_CFUNCTION: + if (cse->cfunc.func->name) { + info.name = apfl_string_view_from(*cse->cfunc.func->name); + } + break; + case APFL_CSE_FUNCTION_DISPATCH: { + struct apfl_string_view sv; + if (apfl_func_get_name(cse->func_dispatch.function, &sv)) { + info.name = sv; + } + info.line_defined = cse->func_dispatch.function->line_defined; + break; + } + case APFL_CSE_MATCHER: + break; + } + + return info; +} + +static bool format_defined_at(struct apfl_format_writer w, struct apfl_call_stack_entry_info info) +{ + FMT_TRY(apfl_format_put_string(w, "defined in line ")); + FMT_TRY(apfl_format_put_int(w, info.line_defined)); + return true; +} + +bool +apfl_call_stack_entry_info_format(struct apfl_format_writer w, struct apfl_call_stack_entry_info info) +{ + switch (info.type) { + case APFL_CSE_FUNCTION: + FMT_TRY(apfl_format_put_string(w, "Line ")); + FMT_TRY(apfl_format_put_int(w, info.line_current)); + FMT_TRY(apfl_format_put_string(w, ", ")); + + if (info.toplevel) { + FMT_TRY(apfl_format_put_string(w, "toplevel ")); + } else if (info.name.len == 0) { + FMT_TRY(apfl_format_put_string(w, "anonymous function (subfunction ")); + FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index)); + FMT_TRY(apfl_format_put_string(w, "; ")); + FMT_TRY(format_defined_at(w, info)); + FMT_TRY(apfl_format_put_string(w, ")")); + } else { + FMT_TRY(apfl_format_put_string(w, "function ")); + FMT_TRY(apfl_format_put_string(w, info.name)); + FMT_TRY(apfl_format_put_string(w, " (subfunction ")); + FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index)); + FMT_TRY(apfl_format_put_string(w, "; ")); + FMT_TRY(format_defined_at(w, info)); + FMT_TRY(apfl_format_put_string(w, ")")); + } + break; + case APFL_CSE_CFUNCTION: + if (info.name.len == 0) { + FMT_TRY(apfl_format_put_string(w, "Anonymous native function")); + } else { + FMT_TRY(apfl_format_put_string(w, "Native function ")); + FMT_TRY(apfl_format_put_string(w, info.name)); + } + break; + case APFL_CSE_FUNCTION_DISPATCH: + FMT_TRY(apfl_format_put_string(w, "Dispatch for ")); + if (info.name.len == 0) { + FMT_TRY(apfl_format_put_string(w, "anonymous function (")); + } else { + FMT_TRY(apfl_format_put_string(w, "function ")); + FMT_TRY(apfl_format_put_string(w, info.name)); + FMT_TRY(apfl_format_put_string(w, "(")); + } + FMT_TRY(format_defined_at(w, info)); + FMT_TRY(apfl_format_put_string(w, ")")); + break; + case APFL_CSE_MATCHER: + FMT_TRY(apfl_format_put_string(w, "Matcher")); + break; + } + + return true; +} diff --git a/src/context.h b/src/context.h index 4288e39..eed519a 100644 --- a/src/context.h +++ b/src/context.h @@ -47,13 +47,6 @@ struct matcher_stack { size_t cap; }; -enum call_stack_entry_type { - CSE_FUNCTION, - CSE_CFUNCTION, - CSE_MATCHER, - CSE_FUNCTION_DISPATCH, -}; - struct func_call_stack_entry { size_t pc; struct instruction_list *instructions; @@ -63,6 +56,9 @@ struct func_call_stack_entry { int execution_line; + struct function *function; // Can be NULL, in that case it's the toplevel + size_t subfunction_index; // Not set, if function == NULL + struct matcher_stack matcher_stack; bool returning_from_matcher; bool matcher_result; @@ -113,7 +109,7 @@ struct func_dispatch_call_stack_entry { }; struct call_stack_entry { - enum call_stack_entry_type type; + enum apfl_call_stack_entry_type type; struct stack stack; diff --git a/src/eval.c b/src/eval.c index 582d25a..d3d0628 100644 --- a/src/eval.c +++ b/src/eval.c @@ -275,7 +275,7 @@ func_inner(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t count) apfl_raise_alloc_error(ctx); } - if ((func_value->func = apfl_func_new(&ctx->gc, count, scope)) == NULL) { + if ((func_value->func = apfl_func_new(&ctx->gc, count, scope, cse->execution_line)) == NULL) { stack_must_drop(ctx, -1); apfl_raise_alloc_error(ctx); } @@ -397,7 +397,7 @@ prepare_call(apfl_ctx ctx, size_t tmproots, struct apfl_value args, struct call_ } // Keep evaluate instructions until we've returned from the current call stack. -// Must not be called with a CSE_CFUNCTION on top of the call stack. +// Must not be called with a APFL_CSE_CFUNCTION on top of the call stack. static void evaluate_until_call_stack_return(apfl_ctx ctx) { @@ -411,16 +411,16 @@ evaluate_until_call_stack_return(apfl_ctx ctx) assert(cse != NULL); switch (cse->type) { - case CSE_CFUNCTION: + case APFL_CSE_CFUNCTION: assert(false); break; - case CSE_FUNCTION: + case APFL_CSE_FUNCTION: evaluate(ctx, &cse->func); break; - case CSE_MATCHER: + case APFL_CSE_MATCHER: evaluate_matcher(ctx, &cse->matcher); break; - case CSE_FUNCTION_DISPATCH: + case APFL_CSE_FUNCTION_DISPATCH: dispatch(ctx, cse); break; } @@ -466,7 +466,7 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid } prepare_call(ctx, tmproots, args, (struct call_stack_entry) { - .type = CSE_FUNCTION_DISPATCH, + .type = APFL_CSE_FUNCTION_DISPATCH, .stack = apfl_stack_new(), .func_dispatch = { .subfunc = 0, @@ -491,7 +491,7 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid } case VALUE_CFUNC: prepare_call(ctx, tmproots, args, (struct call_stack_entry) { - .type = CSE_CFUNCTION, + .type = APFL_CSE_CFUNCTION, .stack = apfl_stack_new(), .cfunc = { .func = func.cfunc, @@ -800,7 +800,7 @@ matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher, struct scopes matcher_cse.matcher_state_stack_cap = 1; if (!try_call_stack_push(ctx, (struct call_stack_entry) { - .type = CSE_MATCHER, + .type = APFL_CSE_MATCHER, .stack = apfl_stack_new(), .matcher = matcher_cse, })) { @@ -1101,7 +1101,7 @@ return_from_matcher(apfl_ctx ctx, bool result) { struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx); assert(cse != NULL); - assert(cse->type == CSE_MATCHER); + assert(cse->type == APFL_CSE_MATCHER); if (result) { matcher_transfer(ctx, &cse->matcher); @@ -1113,11 +1113,11 @@ return_from_matcher(apfl_ctx ctx, bool result) assert(cse != NULL); switch (cse->type) { - case CSE_FUNCTION: + case APFL_CSE_FUNCTION: cse->func.returning_from_matcher = true; cse->func.matcher_result = result; break; - case CSE_FUNCTION_DISPATCH: + case APFL_CSE_FUNCTION_DISPATCH: cse->func_dispatch.returning_from_matcher = true; cse->func_dispatch.matcher_result = result; break; @@ -1297,7 +1297,7 @@ matcher_stack_new(void) static void dispatch(apfl_ctx ctx, struct call_stack_entry *cse) { - assert(cse->type == CSE_FUNCTION_DISPATCH); + assert(cse->type == APFL_CSE_FUNCTION_DISPATCH); struct func_dispatch_call_stack_entry *fd_cse = &cse->func_dispatch; struct function *function = fd_cse->function; @@ -1307,7 +1307,7 @@ dispatch(apfl_ctx ctx, struct call_stack_entry *cse) struct subfunction *subfunction = &function->subfunctions[fd_cse->subfunc]; // Replace the current CSE with a function CSE - cse->type = CSE_FUNCTION; + cse->type = APFL_CSE_FUNCTION; cse->stack.len = 0; cse->func = (struct func_call_stack_entry) { .pc = 0, @@ -1317,6 +1317,8 @@ dispatch(apfl_ctx ctx, struct call_stack_entry *cse) .matcher_stack = matcher_stack_new(), .returning_from_matcher = false, .matcher_result = false, + .function = function, + .subfunction_index = fd_cse->subfunc, }; return; @@ -1376,9 +1378,9 @@ iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr } call_stack_push(ctx, (struct call_stack_entry) { - .type = CSE_FUNCTION, + .type = APFL_CSE_FUNCTION, .stack = apfl_stack_new(), - .func = { + .func = (struct func_call_stack_entry) { .pc = 0, .instructions = ilist, .scopes = { diff --git a/src/globals.c b/src/globals.c index ba748eb..c91b984 100644 --- a/src/globals.c +++ b/src/globals.c @@ -353,6 +353,26 @@ impl_gc(apfl_ctx ctx) apfl_drop(ctx, -1); } +static void +impl_backtrace(apfl_ctx ctx) +{ + apfl_drop(ctx, -1); + + struct apfl_format_writer w = apfl_get_output_writer(ctx); + + size_t depth = apfl_call_stack_depth(ctx); + for (size_t i = 1; i < depth; i++) { + TRY_FORMAT(ctx, apfl_format_put_string(w, "#")); + TRY_FORMAT(ctx, apfl_format_put_int(w, (int)i)); + TRY_FORMAT(ctx, apfl_format_put_string(w, ": ")); + TRY_FORMAT(ctx, apfl_call_stack_entry_info_format( + w, + apfl_call_stack_inspect(ctx, i) + )); + TRY_FORMAT(ctx, apfl_format_put_string(w, "\n")); + } +} + static const struct global_def globals[] = { {"if", impl_if}, {"==", impl_eq}, @@ -375,6 +395,7 @@ static const struct global_def globals[] = { {"type", type}, {"while", impl_while}, {"gc", impl_gc}, + {"backtrace", impl_backtrace}, {NULL, NULL}, }; diff --git a/src/messages.c b/src/messages.c index 1589cbc..20fa4bb 100644 --- a/src/messages.c +++ b/src/messages.c @@ -18,4 +18,5 @@ const struct apfl_messages apfl_messages = { .value_doesnt_match = "Value does not match", .invalid_matcher_state = "Matcher is in invalid state", .no_matching_subfunction = "No matching subfunction", + .invalid_call_stack_index = "Invalid call stack index", }; diff --git a/src/value.c b/src/value.c index 58753b3..ea2bcf3 100644 --- a/src/value.c +++ b/src/value.c @@ -221,7 +221,7 @@ apfl_type_name(enum apfl_value_type type) } struct function * -apfl_func_new(struct gc *gc, size_t cap, struct scope *scope) +apfl_func_new(struct gc *gc, size_t cap, struct scope *scope, int line_defined) { struct subfunction *subfunctions = ALLOC_LIST(gc->allocator, struct subfunction, cap); if (subfunctions == NULL) { @@ -239,6 +239,7 @@ apfl_func_new(struct gc *gc, size_t cap, struct scope *scope) .subfunctions_cap = cap, .scope = scope, .name = NULL, + .line_defined = line_defined, }; return function; diff --git a/src/value.h b/src/value.h index b9dff44..f3d89b4 100644 --- a/src/value.h +++ b/src/value.h @@ -52,6 +52,7 @@ struct function { size_t subfunctions_cap; struct scope *scope; struct apfl_string *name; + int line_defined; }; struct cfunction { @@ -136,7 +137,7 @@ bool apfl_dict_set_raw( size_t apfl_dict_len(struct dict_header *); void apfl_dict_deinit(struct dict_header *); -struct function *apfl_func_new(struct gc *, size_t cap, struct scope *); +struct function *apfl_func_new(struct gc *, size_t cap, struct scope *, int line_defined); bool apfl_func_add_subfunc(struct function *, struct instruction_list *, struct matcher *); bool apfl_func_get_name(struct function *, struct apfl_string_view *name); void apfl_func_set_name(struct function *, struct apfl_string *name);