Add functions to get information about the call stack

This commit is contained in:
Laria 2023-01-26 21:22:34 +01:00
parent 1afec3c7a0
commit 3cc2a83226
8 changed files with 198 additions and 35 deletions

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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 = {

View file

@ -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},
};

View file

@ -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",
};

View file

@ -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;

View file

@ -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);