Add functions to get information about the call stack
This commit is contained in:
parent
1afec3c7a0
commit
3cc2a83226
8 changed files with 198 additions and 35 deletions
23
src/apfl.h
23
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;
|
||||
|
||||
|
|
|
|||
136
src/context.c
136
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
34
src/eval.c
34
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 = {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue