Implement functions and function calls

We can now define and call functions. Lexical closure scopes are also
working :).

It's limited to simple functions or complex functions with a single
argument list of only variable names for now.
This commit is contained in:
Laria 2022-07-11 21:41:05 +02:00
parent eb7cb0baf7
commit a4f7f0f2ff
14 changed files with 1522 additions and 198 deletions

View file

@ -591,9 +591,11 @@ enum apfl_value_type {
APFL_VALUE_LIST,
APFL_VALUE_DICT,
APFL_VALUE_FUNC,
APFL_VALUE_USERDATA,
};
typedef int apfl_stackidx;
typedef size_t apfl_slotidx;
enum apfl_result {
APFL_RESULT_OK, // Evaluation succeeded, value is on the stack
@ -621,6 +623,8 @@ enum apfl_result apfl_iterative_runner_get_result(apfl_iterative_runner);
bool apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner);
void apfl_iterative_runner_destroy(apfl_iterative_runner);
typedef void (*apfl_cfunc)(apfl_ctx);
// Get the type of a value on the stack
enum apfl_value_type apfl_get_type(apfl_ctx, apfl_stackidx);
@ -646,6 +650,22 @@ void apfl_dict_create(apfl_ctx);
void apfl_dict_set(apfl_ctx, apfl_stackidx dict, apfl_stackidx k, apfl_stackidx v);
// Get a value from a container (list or dict) and push it on the stack. container and k will be dropped.
void apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k);
// Get a value from a list and push it on the stack. The list will stay on the stack.
void apfl_get_list_member_by_index(apfl_ctx, apfl_stackidx list, size_t index_in_list);
// Push a C function onto the stack with nslots slots (initialized to nil)
void apfl_push_cfunc(apfl_ctx, apfl_cfunc, size_t nslots);
void apfl_cfunc_getslot(apfl_ctx, apfl_stackidx cfunc, apfl_slotidx);
void apfl_cfunc_self_getslot(apfl_ctx, apfl_slotidx);
void apfl_cfunc_setslot(apfl_ctx, apfl_stackidx cfunc, apfl_slotidx, apfl_stackidx value);
void apfl_cfunc_self_setslot(apfl_ctx, apfl_slotidx, apfl_stackidx value);
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);
bool apfl_debug_print_val(apfl_ctx, apfl_stackidx, struct apfl_format_writer);
@ -662,6 +682,10 @@ struct apfl_messages {
const char *value_is_not_a_container;
const char *wrong_key_type;
const char *variable_doesnt_exist;
const char *not_a_c_function;
const char *invalid_slotidx;
const char *not_a_function;
const char *wrong_type;
};
extern const struct apfl_messages apfl_messages;

View file

@ -52,19 +52,29 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi
case INSN_DICT_APPEND_KVPAIR:
case INSN_GET_MEMBER:
case INSN_NEXT_LINE:
case INSN_DROP:
case INSN_CALL:
break;
case INSN_NUMBER:
case INSN_LIST:
case INSN_SET_LINE:
case INSN_GET_BY_INDEX_KEEP:
i++;
break;
case INSN_STRING:
case INSN_VAR_GET:
case INSN_VAR_SET:
case INSN_VAR_SET_LOCAL:
case INSN_VAR_NEW:
case INSN_VAR_NEW_LOCAL:
case INSN_MOVE_TO_LOCAL_VAR:
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING));
break;
case INSN_FUNC:
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.body, GC_TYPE_INSTRUCTIONS));
break;
}
}
}
@ -99,12 +109,26 @@ apfl_instruction_to_string(enum instruction insn)
return "INSN_VAR_GET";
case INSN_VAR_SET:
return "INSN_VAR_SET";
case INSN_VAR_SET_LOCAL:
return "INSN_VAR_SET_LOCAL";
case INSN_VAR_NEW:
return "INSN_VAR_NEW";
case INSN_VAR_NEW_LOCAL:
return "INSN_VAR_NEW_LOCAL";
case INSN_MOVE_TO_LOCAL_VAR:
return "INSN_MOVE_TO_LOCAL_VAR";
case INSN_NEXT_LINE:
return "INSN_NEXT_LINE";
case INSN_SET_LINE:
return "INSN_SET_LINE";
case INSN_GET_BY_INDEX_KEEP:
return "INSN_GET_BY_INDEX_KEEP";
case INSN_DROP:
return "INSN_DROP";
case INSN_CALL:
return "INSN_CALL";
case INSN_FUNC:
return "INSN_FUNC";
}
return "??";

View file

@ -21,11 +21,18 @@ enum instruction {
INSN_DICT, // ( -- dict )
INSN_DICT_APPEND_KVPAIR, // ( dict key value -- dict' )
INSN_GET_MEMBER, // ( list/dict key -- value )
INSN_GET_BY_INDEX_KEEP, // ( list/dict -- list/dict value ), arg: index
INSN_VAR_GET, // ( -- value ), arg: string
INSN_VAR_SET, // ( value -- ), arg: string
INSN_VAR_SET, // ( value -- value ), arg: string
INSN_VAR_SET_LOCAL, // ( value -- value ), arg: string
INSN_VAR_NEW, // ( -- ), arg: string
INSN_VAR_NEW_LOCAL, // ( -- ), arg: string
INSN_MOVE_TO_LOCAL_VAR, // ( value -- ), arg: string
INSN_NEXT_LINE, // ( -- )
INSN_SET_LINE, // ( -- ), arg: count (new line number)
INSN_DROP, // ( value -- )
INSN_CALL, // ( func list -- value )
INSN_FUNC, // ( -- func ), arg: body
};
union instruction_or_arg {
@ -33,6 +40,8 @@ union instruction_or_arg {
struct apfl_string *string;
apfl_number number;
size_t count;
size_t index;
struct instruction_list *body;
};
struct instruction_list {

View file

@ -9,7 +9,7 @@
#include "resizable.h"
#include "strings.h"
#define DEBUG_COMPILING 1
#define DEBUG_COMPILING 0
#if DEBUG_COMPILING
# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__);
@ -91,6 +91,18 @@ ilist_put_string(struct instruction_list *ilist, struct apfl_string *string)
ILIST_PUT(ilist, string, string, "put_string: " APFL_STR_FMT, APFL_STR_FMT_ARGS(*string))
}
static void
ilist_put_body(struct instruction_list *ilist, struct instruction_list *body)
{
ILIST_PUT(ilist, body, body, "put_body: %p", (void *)body)
}
static void
ilist_put_index(struct instruction_list *ilist, size_t index)
{
ILIST_PUT(ilist, index, index, "put_index: %d", (int)index)
}
static bool
string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in)
{
@ -220,25 +232,44 @@ compile_simple_assignment(
struct compiler *compiler,
struct apfl_string *var,
struct apfl_expr *rhs,
struct instruction_list *ilist
struct instruction_list *ilist,
enum instruction new_insn,
enum instruction set_insn
) {
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct apfl_string *str;
TRY(string_move_into_new(compiler, &str, var));
ilist_put_insn(ilist, INSN_VAR_NEW);
ilist_put_insn(ilist, new_insn);
ilist_put_string(ilist, str);
TRY(compile_expr(compiler, rhs, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_VAR_SET);
ilist_put_insn(ilist, set_insn);
ilist_put_string(ilist, str);
return true;
}
static struct instruction_list *
tmp_ilist(struct compiler *compiler, int line)
{
struct instruction_list *ilist;
if (
(ilist = apfl_instructions_new(compiler->gc, line)) == NULL
|| !apfl_gc_tmproot_add(
compiler->gc,
GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS)
)
) {
return NULL;
}
return ilist;
}
static bool
compile_assignment(
struct compiler *compiler,
@ -253,7 +284,9 @@ compile_assignment(
compiler,
&assignment->lhs.var_or_member.var,
assignment->rhs,
ilist
ilist,
assignment->local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW,
assignment->local ? INSN_VAR_SET_LOCAL : INSN_VAR_SET
);
}
@ -262,6 +295,119 @@ compile_assignment(
return false;
}
static bool
compile_call(struct compiler *compiler, struct apfl_expr_call *call, struct instruction_list *ilist)
{
TRY(compile_expr(compiler, call->callee, ilist));
TRY(compile_list(compiler, &call->arguments, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_CALL);
return true;
}
static bool
compile_body(struct compiler *compiler, struct apfl_expr_body *body, struct instruction_list *ilist)
{
for (size_t i = 0; i < body->len; i++) {
TRY(compile_expr(compiler, &body->items[i], ilist));
}
return true;
}
static bool
compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line)
{
struct instruction_list *body_ilist = NULL;
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line)));
// Drop the argument list, we ignore it in simple functions
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
ilist_put_insn(body_ilist, INSN_DROP);
TRY(compile_body(compiler, func, body_ilist));
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_FUNC);
ilist_put_body(ilist, body_ilist);
return true;
}
static bool
compile_simple_func(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line)
{
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
bool ok = compile_simple_func_inner(compiler, func, ilist, line);
apfl_gc_tmproots_restore(compiler->gc, tmproots);
return ok;
}
static bool
compile_complex_func_inner(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line)
{
if (func->len != 1) {
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
return false;
}
struct apfl_expr_subfunc *subfunc = &func->subfuncs[0];
struct instruction_list *body_ilist = NULL;
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line)));
for (size_t i = 0; i < subfunc->params.len; i++) {
struct apfl_expr_params_item *param = &subfunc->params.params[i];
if (param->expand) {
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
return false;
}
switch (param->param.type) {
case APFL_EXPR_PARAM_VAR:
{
TRY(ilist_ensure_cap(compiler, body_ilist, 4));
struct apfl_string *str;
TRY(string_move_into_new(compiler, &str, &param->param.var));
ilist_put_insn(body_ilist, INSN_GET_BY_INDEX_KEEP);
ilist_put_index(body_ilist, i);
ilist_put_insn(body_ilist, INSN_MOVE_TO_LOCAL_VAR);
ilist_put_string(body_ilist, str);
break;
}
case APFL_EXPR_PARAM_CONSTANT:
case APFL_EXPR_PARAM_PREDICATE:
case APFL_EXPR_PARAM_LIST:
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
return false;
case APFL_EXPR_PARAM_BLANK:
break;
}
}
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
ilist_put_insn(body_ilist, INSN_DROP); // Drop the argument list
TRY(compile_body(compiler, &subfunc->body, body_ilist));
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_FUNC);
ilist_put_body(ilist, body_ilist);
return true;
}
static bool
compile_complex_func(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line)
{
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
bool ok = compile_complex_func_inner(compiler, func, ilist, line);
apfl_gc_tmproots_restore(compiler->gc, tmproots);
return ok;
}
static bool
compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instruction_list *ilist)
{
@ -281,10 +427,11 @@ compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instructi
switch (expr->type) {
case APFL_EXPR_CALL:
return compile_call(compiler, &expr->call, ilist);
case APFL_EXPR_SIMPLE_FUNC:
return compile_simple_func(compiler, &expr->simple_func, ilist, new_line);
case APFL_EXPR_COMPLEX_FUNC:
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
return false;
return compile_complex_func(compiler, &expr->complex_func, ilist, new_line);
case APFL_EXPR_CONSTANT:
return compile_constant(compiler, &expr->constant, ilist);
case APFL_EXPR_BLANK:

View file

@ -13,6 +13,7 @@
#include "value.h"
static bool try_push_const_string(apfl_ctx ctx, const char *string);
static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx);
APFL_NORETURN static void
panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result)
@ -25,14 +26,12 @@ panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result)
abort();
}
#define RESULT_OFF_FOR_LONGJMP 1
enum apfl_result
apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *with_error_on_stack)
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack)
{
struct error_handler *prev_handler = ctx->error_handler;
size_t stack_len = ctx->stack.len;
size_t callstack_len = ctx->call_stack.len;
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
struct error_handler handler;
@ -41,7 +40,7 @@ apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *wit
enum apfl_result result = APFL_RESULT_OK;
int rv = setjmp(handler.jump);
if (rv == 0) {
cb(ctx, opaque);
apfl_call(ctx, func, args);
} else {
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
assert(result != APFL_RESULT_OK);
@ -55,8 +54,23 @@ apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *wit
}
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
assert(stack_len <= ctx->stack.cap);
ctx->stack.len = stack_len;
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]);
}
assert(
// Shrinking should not fail
apfl_resizable_resize(
ctx->gc.allocator,
sizeof(struct call_stack_entry),
(void **)&ctx->call_stack.items,
&ctx->call_stack.len,
&ctx->call_stack.cap,
callstack_len
)
);
if (*with_error_on_stack) {
if (!apfl_stack_push(ctx, err)) {
@ -91,7 +105,7 @@ raise_error(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result type)
APFL_NORETURN void
apfl_raise_error_with_type(apfl_ctx ctx, apfl_stackidx idx, enum apfl_result type)
{
bool ok = apfl_stack_move_to_top(ctx, idx);
bool ok = current_stack_move_to_top(ctx, idx);
raise_error(ctx, ok, type);
}
@ -145,8 +159,8 @@ apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque)
ctx->panic_callback_data = opaque;
}
static struct stack
stack_new()
struct stack
apfl_stack_new(void)
{
return (struct stack) {
.items = NULL,
@ -155,6 +169,15 @@ stack_new()
};
}
static struct stack *
current_value_stack(apfl_ctx ctx)
{
struct call_stack_entry *csentry = apfl_call_stack_cur_entry(ctx);
return csentry != NULL
? &csentry->stack
: &ctx->toplevel_stack;
}
void
apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value)
{
@ -166,34 +189,48 @@ apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value)
bool
apfl_stack_push(apfl_ctx ctx, struct apfl_value value)
{
struct stack *stack = current_value_stack(ctx);
return apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct apfl_value),
(void **)&ctx->stack.items,
&ctx->stack.len,
&ctx->stack.cap,
(void **)&stack->items,
&stack->len,
&stack->cap,
&value,
1
);
}
bool
apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index)
static bool
stack_check_index(struct stack *stack, apfl_stackidx *index)
{
if (*index < 0) {
if ((size_t)-*index > ctx->stack.len) {
if ((size_t)-*index > stack->len) {
return false;
}
*index = ctx->stack.len + *index;
} else if ((size_t)*index >= ctx->stack.len) {
*index = stack->len + *index;
} else if ((size_t)*index >= stack->len) {
return false;
}
assert(0 <= *index && (size_t)*index < ctx->stack.len);
assert(0 <= *index && (size_t)*index < stack->len);
return true;
}
bool
apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index)
{
return stack_check_index(current_value_stack(ctx), index);
}
bool
apfl_stack_has_index(apfl_ctx ctx, apfl_stackidx index)
{
return apfl_stack_check_index(ctx, &index);
}
static int
cmp_stackidx(const void *_a, const void *_b)
{
@ -202,10 +239,13 @@ cmp_stackidx(const void *_a, const void *_b)
return *a - *b;
}
bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
bool
apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
{
struct stack *stack = current_value_stack(ctx);
for (size_t i = 0; i < count; i++) {
if (!apfl_stack_check_index(ctx, &indices[i])) {
if (!stack_check_index(stack, &indices[i])) {
return false;
}
}
@ -216,8 +256,8 @@ bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
// Will not fail, as we've already checked the indices
assert(apfl_resizable_cut_without_resize(
sizeof(struct apfl_value),
(void **)&ctx->stack.items,
&ctx->stack.len,
(void **)&stack->items,
&stack->len,
indices[i],
1
));
@ -231,18 +271,20 @@ bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
bool
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
{
if (!apfl_stack_check_index(ctx, &index)) {
struct stack *stack = current_value_stack(ctx);
if (!stack_check_index(stack, &index)) {
return false;
}
*value = ctx->stack.items[index];
*value = stack->items[index];
assert(apfl_resizable_splice(
ctx->gc.allocator,
sizeof(struct apfl_value),
(void **)&ctx->stack.items,
&ctx->stack.len,
&ctx->stack.cap,
(void **)&stack->items,
&stack->len,
&stack->cap,
index,
1,
NULL,
@ -252,32 +294,60 @@ apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
return true;
}
struct apfl_value
apfl_stack_must_pop(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value;
if (!apfl_stack_pop(ctx, &value, index)) {
apfl_raise_invalid_stackidx(ctx);
}
return value;
}
static struct apfl_value *
stack_get_pointer(apfl_ctx ctx, apfl_stackidx index)
{
if (!apfl_stack_check_index(ctx, &index)) {
struct stack *stack = current_value_stack(ctx);
if (!stack_check_index(stack, &index)) {
return NULL;
}
return &ctx->stack.items[index];
return &stack->items[index];
}
static bool
stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index)
stack_get_and_adjust_index(struct stack *stack, struct apfl_value *value, apfl_stackidx *index)
{
if (!apfl_stack_check_index(ctx, index)) {
if (!stack_check_index(stack, index)) {
return false;
}
*value = ctx->stack.items[*index];
*value = stack->items[*index];
return true;
}
static bool
current_stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index)
{
return stack_get_and_adjust_index(current_value_stack(ctx), value, index);
}
bool
apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
{
return stack_get_and_adjust_index(ctx, value, &index);
return current_stack_get_and_adjust_index(ctx, value, &index);
}
struct apfl_value
apfl_stack_must_get(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value;
if (!apfl_stack_get(ctx, &value, index)) {
apfl_raise_invalid_stackidx(ctx);
}
return value;
}
struct apfl_value *
@ -314,25 +384,27 @@ apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
return apfl_stack_pop(ctx, &value, index);
}
bool
apfl_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
static bool
current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
{
struct stack *stack = current_value_stack(ctx);
struct apfl_value val;
if (!stack_get_and_adjust_index(ctx, &val, &index)) {
if (!stack_get_and_adjust_index(stack, &val, &index)) {
return false;
}
size_t absindex = (size_t)index;
// If we're here, index is an absolute address and is guaranteed to be < len
assert(ctx->stack.len >= absindex+1);
assert(stack->len >= absindex+1);
memmove(
&ctx->stack.items[absindex + 1],
&ctx->stack.items[absindex],
ctx->stack.len - absindex - 1
&stack->items[absindex + 1],
&stack->items[absindex],
stack->len - absindex - 1
);
ctx->stack.items[ctx->stack.len-1] = val;
stack->items[stack->len-1] = val;
return true;
}
@ -340,30 +412,113 @@ apfl_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
void
apfl_stack_clear(apfl_ctx ctx)
{
ctx->stack.len = 0;
struct stack *stack = current_value_stack(ctx);
stack->len = 0;
}
static void
stack_traverse(struct stack *stack, gc_visitor visitor, void *opaque)
stack_traverse(struct stack stack, gc_visitor visitor, void *opaque)
{
for (size_t i = 0; i < stack->len; i++) {
struct gc_object *object = apfl_value_get_gc_object(stack->items[i]);
for (size_t i = 0; i < stack.len; i++) {
struct gc_object *object = apfl_value_get_gc_object(stack.items[i]);
if (object != NULL) {
visitor(opaque, object);
}
}
}
static void
visit_nullable_scope(struct scope *scope, gc_visitor visitor, void *opaque)
{
if (scope != NULL) {
visitor(opaque, GC_OBJECT_FROM(scope, GC_TYPE_SCOPE));
}
}
static void
gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, void *opaque)
{
stack_traverse(cse.stack, visitor, opaque);
switch (cse.type) {
case CSE_FUNCTION:
visitor(
opaque,
GC_OBJECT_FROM(cse.func.instructions, GC_TYPE_INSTRUCTIONS)
);
visit_nullable_scope(cse.func.scope, visitor, opaque);
visit_nullable_scope(cse.func.closure_scope, visitor, opaque);
break;
case CSE_CFUNCTION:
visitor(
opaque,
GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC)
);
break;
}
}
static void
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
{
apfl_ctx ctx = own_opaque;
if (ctx->scope != NULL) {
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->scope, GC_TYPE_SCOPE));
if (ctx->globals != NULL) {
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->globals, GC_TYPE_SCOPE));
}
stack_traverse(&ctx->stack, visitor, visitor_opaque);
stack_traverse(ctx->toplevel_stack, visitor, visitor_opaque);
for (size_t i = 0; i < ctx->call_stack.len; i++) {
gc_traverse_call_stack_entry(ctx->call_stack.items[i], visitor, visitor_opaque);
}
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
apfl_iterative_runner_visit_gc_objects(ctx->iterative_runners.items[i], visitor, visitor_opaque);
}
}
static struct call_stack
call_stack_new(void)
{
return (struct call_stack) {
.items = NULL,
.len = 0,
.cap = 0,
};
}
static void
deinit_stack(struct apfl_allocator allocator, struct stack *stack)
{
FREE_LIST(allocator, stack->items, stack->cap);
*stack = apfl_stack_new();
}
void
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
{
deinit_stack(allocator, &entry->stack);
}
struct call_stack_entry *
apfl_call_stack_cur_entry(apfl_ctx ctx)
{
return ctx->call_stack.len == 0
? NULL
: &ctx->call_stack.items[ctx->call_stack.len - 1];
}
static struct iterative_runners_list
iterative_runners_list_new(void)
{
return (struct iterative_runners_list) {
.items = NULL,
.len = 0,
.cap = 0,
};
}
apfl_ctx
@ -376,16 +531,19 @@ apfl_ctx_new(struct apfl_allocator base_allocator)
apfl_gc_init(&ctx->gc, base_allocator, get_roots, ctx);
if ((ctx->scope = apfl_scope_new(&ctx->gc)) == NULL) {
ctx->toplevel_stack = apfl_stack_new();
ctx->call_stack = call_stack_new();
if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) {
goto error;
}
ctx->stack = stack_new();
ctx->error_handler = NULL;
ctx->panic_callback = NULL;
ctx->panic_callback_data = NULL;
ctx->iterative_runners = iterative_runners_list_new();
return ctx;
error:
@ -400,17 +558,90 @@ apfl_ctx_destroy(apfl_ctx ctx)
return;
}
deinit_stack(ctx->gc.allocator, &ctx->toplevel_stack);
ctx->toplevel_stack = apfl_stack_new();
DEINIT_CAP_LIST(
ctx->gc.allocator,
ctx->call_stack.items,
ctx->call_stack.len,
ctx->call_stack.cap,
apfl_call_stack_entry_deinit
);
ctx->call_stack = call_stack_new();
FREE_LIST(ctx->gc.allocator, ctx->iterative_runners.items, ctx->iterative_runners.cap);
ctx->iterative_runners = iterative_runners_list_new();
struct apfl_allocator base_allocator = ctx->gc.base_allocator;
apfl_gc_full(&ctx->gc);
apfl_gc_deinit(&ctx->gc);
FREE_LIST(ctx->gc.allocator, ctx->stack.items, ctx->stack.cap);
ctx->stack = stack_new();
FREE_OBJ(base_allocator, ctx);
}
static bool
find_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner, size_t *index)
{
// It should be very uncommon that there are a lot of iterative runners
// existing on the same apfl_ctx at the same time, so a linear scan should
// be good enough :)
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
if (ctx->iterative_runners.items[i] == runner) {
if (index != NULL) {
*index = i;
}
return true;
}
}
return false;
}
bool
apfl_ctx_register_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
{
if (find_iterative_runner(ctx, runner, NULL)) {
return true;
}
return apfl_resizable_append(
ctx->gc.allocator,
sizeof(apfl_iterative_runner),
(void **)&ctx->iterative_runners.items,
&ctx->iterative_runners.len,
&ctx->iterative_runners.cap,
&runner,
1
);
}
void
apfl_ctx_unregister_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
{
size_t i;
if (!find_iterative_runner(ctx, runner, &i)) {
return;
}
assert(
// We're only removing elements, the buffer should not grow,
// therefore there should be no allocation errors
apfl_resizable_splice(
ctx->gc.allocator,
sizeof(apfl_iterative_runner),
(void **)&ctx->iterative_runners.items,
&ctx->iterative_runners.len,
&ctx->iterative_runners.cap,
i,
1,
NULL,
0
)
);
}
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
struct apfl_value *value = apfl_stack_push_placeholder(ctx); \
if (value == NULL) { \
@ -519,10 +750,7 @@ apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_ind
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
}
struct apfl_value value;
if (!apfl_stack_get(ctx, &value, value_index)) {
apfl_raise_invalid_stackidx(ctx);
}
struct apfl_value value = apfl_stack_must_get(ctx, value_index);
if (!apfl_list_splice(
&ctx->gc,
@ -532,6 +760,7 @@ apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_ind
&value,
1
)) {
assert(apfl_stack_drop(ctx, value_index));
apfl_raise_alloc_error(ctx);
}
@ -550,10 +779,7 @@ apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_i
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
}
struct apfl_value src_val;
if (!apfl_stack_get(ctx, &src_val, src_index)) {
apfl_raise_invalid_stackidx(ctx);
}
struct apfl_value src_val = apfl_stack_must_get(ctx, src_index);
if (src_val.type != VALUE_LIST) {
assert(apfl_stack_drop(ctx, src_index));
@ -626,8 +852,8 @@ apfl_get_member(
struct apfl_value container;
struct apfl_value k;
if (
!stack_get_and_adjust_index(ctx, &container, &container_index)
|| !stack_get_and_adjust_index(ctx, &k, &k_index)
!current_stack_get_and_adjust_index(ctx, &container, &container_index)
|| !current_stack_get_and_adjust_index(ctx, &k, &k_index)
) {
apfl_raise_invalid_stackidx(ctx);
}
@ -659,3 +885,196 @@ apfl_get_member(
break;
}
}
void
apfl_get_list_member_by_index(
apfl_ctx ctx,
apfl_stackidx list_index,
size_t index
) {
struct apfl_value list = apfl_stack_must_get(ctx, list_index);
if (list.type != VALUE_LIST) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
}
if (index >= list.list->len) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
}
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
if (value == NULL) {
apfl_raise_alloc_error(ctx);
}
*value = apfl_value_set_cow_flag(list.list->items[index]);
return;
}
static struct scope *
closure_scope_for_func_inner(apfl_ctx ctx)
{
struct scope *out = apfl_scope_new(&ctx->gc);
if (out == NULL) {
return NULL;
}
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(out, GC_TYPE_SCOPE))) {
return NULL;
}
struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx);
if (entry == NULL || entry->type != CSE_FUNCTION) {
return out;
}
// The order is important here: by merging entry->scope last, we make sure a
// variable from the current scope shadows a variable from the closure scope
if (entry->func.closure_scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.closure_scope)) {
return NULL;
}
}
if (entry->func.scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.scope)) {
return NULL;
}
}
return out;
}
struct scope *
apfl_closure_scope_for_func(apfl_ctx ctx)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
struct scope *out = closure_scope_for_func_inner(ctx);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
return out;
}
void
apfl_push_cfunc(apfl_ctx ctx, apfl_cfunc cfunc, size_t nslots)
{
CREATE_GC_OBJECT_VALUE_ON_STACK(
ctx,
VALUE_CFUNC,
cfunc,
apfl_cfunc_new(&ctx->gc, cfunc, nslots)
)
}
static APFL_NORETURN void
raise_no_cfunc(apfl_ctx ctx)
{
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_c_function);
}
static struct cfunction *
must_get_cfunc(apfl_ctx ctx, apfl_stackidx idx)
{
struct apfl_value value = apfl_stack_must_get(ctx, idx);
if (value.type != VALUE_CFUNC) {
raise_no_cfunc(ctx);
}
return value.cfunc;
}
static struct cfunction *
must_get_cfunc_self(apfl_ctx ctx)
{
struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx);
if (entry == NULL) {
raise_no_cfunc(ctx);
}
if (entry->type != CSE_CFUNCTION) {
raise_no_cfunc(ctx);
}
return entry->cfunc.func;
}
static struct apfl_value **
cfunc_getslotvar(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot)
{
if (slot >= cfunction->slots_len) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_slotidx);
}
return &cfunction->slots[slot];
}
static void
cfunc_getslot(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot)
{
struct apfl_value **var = cfunc_getslotvar(ctx, cfunction, slot);
if (*var == NULL) {
apfl_push_nil(ctx);
return;
}
// The value is now in the slot and on the stack. We need to set the COW
// flag so a mutation of one copy doesn't affect the other one.
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(**var));
}
void
apfl_cfunc_getslot(apfl_ctx ctx, apfl_stackidx cfunc, apfl_slotidx slot)
{
cfunc_getslot(ctx, must_get_cfunc(ctx, cfunc), slot);
}
void
apfl_cfunc_self_getslot(apfl_ctx ctx, apfl_slotidx slot)
{
cfunc_getslot(ctx, must_get_cfunc_self(ctx), slot);
}
static void
cfunc_setslot(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot, apfl_stackidx value)
{
struct apfl_value **var = cfunc_getslotvar(ctx, cfunction, slot);
if (*var == NULL) {
*var = apfl_gc_new_var(&ctx->gc);
if (*var == NULL) {
apfl_raise_alloc_error(ctx);
}
**var = (struct apfl_value) { .type = VALUE_NIL };
}
**var = apfl_stack_must_pop(ctx, value);
}
void
apfl_cfunc_setslot(apfl_ctx ctx, apfl_stackidx cfunc, apfl_slotidx slot, apfl_stackidx value)
{
cfunc_setslot(ctx, must_get_cfunc(ctx, cfunc), slot, value);
}
void
apfl_cfunc_self_setslot(apfl_ctx ctx, apfl_slotidx slot, apfl_stackidx value)
{
cfunc_setslot(ctx, must_get_cfunc_self(ctx), slot, value);
}
void
apfl_push_userdata(apfl_ctx ctx, void *userdata)
{
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_USERDATA,
.userdata = userdata,
});
}
void *apfl_get_userdata(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_pop(ctx, index);
if (value.type != VALUE_USERDATA) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
}
return value.userdata;
}

View file

@ -14,27 +14,76 @@ extern "C" {
#include "value.h"
#include "scope.h"
#define RESULT_OFF_FOR_LONGJMP 1
struct stack {
struct apfl_value *items;
size_t len;
size_t cap;
};
enum call_stack_entry_type {
CSE_FUNCTION,
CSE_CFUNCTION,
};
struct func_call_stack_entry {
size_t pc;
struct instruction_list *instructions;
// Both scope and closure_scope can be null (scope will be created lazily)
struct scope *scope;
struct scope *closure_scope;
int execution_line;
};
struct cfunc_call_stack_entry {
struct cfunction *func;
};
struct call_stack_entry {
enum call_stack_entry_type type;
struct stack stack;
union {
struct func_call_stack_entry func;
struct cfunc_call_stack_entry cfunc;
};
};
struct call_stack {
struct call_stack_entry *items;
size_t len;
size_t cap;
};
struct error_handler {
jmp_buf jump;
bool with_error_on_stack;
};
struct iterative_runners_list {
apfl_iterative_runner *items;
size_t len;
size_t cap;
};
struct apfl_ctx_data {
struct gc gc;
apfl_panic_callback panic_callback;
void *panic_callback_data;
struct scope *scope;
struct stack stack;
struct stack toplevel_stack;
struct scope *globals;
struct call_stack call_stack;
struct error_handler *error_handler;
int execution_line;
struct iterative_runners_list iterative_runners;
};
APFL_NORETURN void apfl_raise_error_with_type(apfl_ctx, apfl_stackidx, enum apfl_result type);
@ -43,19 +92,32 @@ APFL_NORETURN void apfl_raise_alloc_error(apfl_ctx);
APFL_NORETURN void apfl_raise_invalid_stackidx(apfl_ctx);
APFL_NORETURN void apfl_raise_error_object(apfl_ctx, struct apfl_error);
void apfl_call_stack_entry_deinit(struct apfl_allocator, struct call_stack_entry *);
struct stack apfl_stack_new(void);
bool apfl_stack_push(apfl_ctx, struct apfl_value);
void apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value);
bool apfl_stack_check_index(apfl_ctx, apfl_stackidx *);
bool apfl_stack_has_index(apfl_ctx, apfl_stackidx);
bool apfl_stack_pop(apfl_ctx, struct apfl_value *value, apfl_stackidx);
struct apfl_value apfl_stack_must_pop(apfl_ctx, apfl_stackidx);
bool apfl_stack_get(apfl_ctx, struct apfl_value *value, apfl_stackidx);
struct apfl_value apfl_stack_must_get(apfl_ctx, apfl_stackidx);
bool apfl_stack_drop(apfl_ctx, apfl_stackidx);
bool apfl_stack_move_to_top(apfl_ctx, apfl_stackidx);
bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices);
void apfl_stack_clear(apfl_ctx);
struct apfl_value *apfl_stack_push_placeholder(apfl_ctx);
bool apfl_move_string_onto_stack(apfl_ctx, struct apfl_string);
typedef void (*apfl_protected_callback)(apfl_ctx, void *);
enum apfl_result apfl_protected(apfl_ctx, apfl_protected_callback, void *, bool *with_error_on_stack);
struct call_stack_entry *apfl_call_stack_cur_entry(apfl_ctx);
struct scope *apfl_closure_scope_for_func(apfl_ctx);
bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner);
void apfl_ctx_unregister_iterative_runner(apfl_ctx, apfl_iterative_runner);
void apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner, gc_visitor, void *);
#ifdef __cplusplus
}

View file

@ -8,9 +8,12 @@
#include "context.h"
#include "format.h"
#include "hashmap.h"
#include "resizable.h"
#include "strings.h"
#include "value.h"
static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse);
static void
stack_must_drop(apfl_ctx ctx, apfl_stackidx index)
{
@ -36,28 +39,135 @@ must_get_argument(apfl_ctx ctx, size_t *i, struct instruction_list *ilist, union
}
}
static void
variable_get(apfl_ctx ctx, struct apfl_string *name)
static struct func_call_stack_entry *
get_current_func_cse(apfl_ctx ctx)
{
struct apfl_value value;
if (!apfl_scope_get(ctx->scope, name, &value)) {
// TODO: Include variable name in error
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.variable_doesnt_exist);
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
if (cse == NULL) {
return NULL;
}
apfl_stack_must_push(ctx, value);
return cse->type == CSE_FUNCTION
? &cse->func
: NULL;
}
enum scope_type {
SCOPE_LOCAL,
SCOPE_CLOSUE,
SCOPE_GLOBAL,
};
static struct scope *
get_scope(apfl_ctx ctx, enum scope_type type)
{
struct func_call_stack_entry *func_cse;
switch (type) {
case SCOPE_LOCAL:
if ((func_cse = get_current_func_cse(ctx)) != NULL) {
return func_cse->scope;
}
return NULL;
case SCOPE_CLOSUE:
if ((func_cse = get_current_func_cse(ctx)) != NULL) {
return func_cse->closure_scope;
}
return NULL;
case SCOPE_GLOBAL:
return ctx->globals;
}
assert(false);
return NULL;
}
static struct scope *
get_or_create_local_scope(apfl_ctx ctx)
{
struct func_call_stack_entry *func_cse = get_current_func_cse(ctx);
assert(func_cse != NULL);
if (func_cse->scope != NULL) {
return func_cse->scope;
}
if ((func_cse->scope = apfl_scope_new(&ctx->gc)) == NULL) {
apfl_raise_alloc_error(ctx);
}
return func_cse->scope;
}
static bool
try_variable_get_for_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type)
{
struct apfl_value value;
struct scope *scope;
if ((scope = get_scope(ctx, type)) != NULL) {
if (apfl_scope_get(scope, name, &value)) {
apfl_stack_must_push(ctx, value);
return true;
}
}
return false;
}
static void
variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack)
variable_get(apfl_ctx ctx, struct apfl_string *name)
{
struct apfl_value value;
if (!apfl_stack_get(ctx, &value, -1)) {
apfl_raise_invalid_stackidx(ctx);
if (try_variable_get_for_scope_type(ctx, name, SCOPE_LOCAL)) {
return;
}
if (!apfl_scope_set(&ctx->gc, ctx->scope, name, value)) {
apfl_raise_alloc_error(ctx);
if (try_variable_get_for_scope_type(ctx, name, SCOPE_CLOSUE)) {
return;
}
if (try_variable_get_for_scope_type(ctx, name, SCOPE_GLOBAL)) {
return;
}
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.variable_doesnt_exist);
}
static bool
try_variable_update_existing_for_scope_type(
apfl_ctx ctx,
struct apfl_string *name,
struct apfl_value value,
enum scope_type type
) {
struct scope *scope;
if ((scope = get_scope(ctx, type)) != NULL) {
if (apfl_scope_update_existing(scope, name, value)) {
return true;
}
}
return false;
}
static void
variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
bool was_set = false;
if (!local) {
was_set = try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_LOCAL)
|| try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_CLOSUE);
}
if (!was_set) {
struct scope *scope = get_or_create_local_scope(ctx);
assert(scope != NULL /*get_or_create_local_scope should never return NULL*/);
if (!apfl_scope_set(&ctx->gc, scope, name, value)) {
apfl_raise_alloc_error(ctx);
}
}
if (keep_on_stack) {
// If the value should be kept on the stack, the value is now in two
// places. We need to set the COW flag to prevent mutations of one copy
@ -68,97 +178,363 @@ variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack)
}
}
static void
variable_new(apfl_ctx ctx, struct apfl_string *name)
static bool
variable_exists_by_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type)
{
if (!apfl_scope_create_var(&ctx->gc, ctx->scope, name)) {
struct scope *scope;
if ((scope = get_scope(ctx, type)) != NULL) {
if (apfl_scope_has(scope, name)) {
return true;
}
}
return false;
}
static void
variable_new(apfl_ctx ctx, struct apfl_string *name, bool local)
{
if (!local) {
if (variable_exists_by_scope_type(ctx, name, SCOPE_LOCAL)) {
return;
}
if (variable_exists_by_scope_type(ctx, name, SCOPE_CLOSUE)) {
return;
}
}
struct scope *scope = get_or_create_local_scope(ctx);
if (!apfl_scope_create_var(&ctx->gc, scope, name)) {
apfl_raise_alloc_error(ctx);
}
}
static void
evaluate(apfl_ctx ctx, size_t *i, struct instruction_list *ilist)
func_inner(apfl_ctx ctx, struct instruction_list *body)
{
struct scope *scope = apfl_closure_scope_for_func(ctx);
if (scope == NULL) {
apfl_raise_alloc_error(ctx);
}
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(scope, GC_TYPE_SCOPE))) {
apfl_raise_alloc_error(ctx);
}
struct apfl_value *func_value = apfl_stack_push_placeholder(ctx);
if (func_value == NULL) {
apfl_raise_alloc_error(ctx);
}
if ((func_value->func = apfl_func_new(&ctx->gc, body, scope)) == NULL) {
stack_must_drop(ctx, -1);
apfl_raise_alloc_error(ctx);
}
func_value->type = VALUE_FUNC;
}
static void
func(apfl_ctx ctx, struct instruction_list *body)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
func_inner(ctx, body);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
{
if (!apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct call_stack_entry),
(void**)&ctx->call_stack.items,
&ctx->call_stack.len,
&ctx->call_stack.cap,
&cse,
1
)) {
apfl_call_stack_entry_deinit(ctx->gc.allocator, &cse);
apfl_raise_alloc_error(ctx);
}
}
static void
return_from_function_inner(apfl_ctx ctx)
{
struct apfl_value value;
if (!apfl_stack_pop(ctx, &value, -1)) {
// No return value on the stack. Return nil instead
value = (struct apfl_value) { .type = VALUE_NIL };
}
assert(ctx->call_stack.len > 0);
apfl_call_stack_entry_deinit(ctx->gc.allocator, apfl_call_stack_cur_entry(ctx));
assert(
// We're shrinking the memory here, should not fail
apfl_resizable_resize(
ctx->gc.allocator,
sizeof(struct call_stack_entry),
(void **)&ctx->call_stack.items,
&ctx->call_stack.len,
&ctx->call_stack.cap,
ctx->call_stack.len - 1
)
);
apfl_stack_must_push(ctx, value);
}
static void
return_from_function(apfl_ctx ctx)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
return_from_function_inner(ctx);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
prepare_call(apfl_ctx ctx, size_t tmproots, struct apfl_value args, struct call_stack_entry cse)
{
call_stack_push(ctx, cse);
// Note: This pushes args on the stack of the newly created call stack
apfl_stack_must_push(ctx, args);
// Both the function and the args are now rooted again, we can undo the tmproots early.
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
// Keep evaluate instructions until we've returned from the current call stack.
// Must only be called with a CSE_FUNCTION on top of the call stack.
static void
evaluate_until_call_stack_return(apfl_ctx ctx)
{
struct call_stack *call_stack = &ctx->call_stack;
size_t depth_started = call_stack->len;
assert(depth_started > 0);
while (call_stack->len >= depth_started) {
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
assert(cse->type == CSE_FUNCTION);
evaluate(ctx, &cse->func);
}
}
static void
must_tmproot_add_value(apfl_ctx ctx, struct apfl_value value)
{
struct gc_object *obj = apfl_value_get_gc_object(value);
if (obj != NULL) {
if (!apfl_gc_tmproot_add(&ctx->gc, obj)) {
apfl_raise_alloc_error(ctx);
}
}
}
static void
call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackidx args_index, bool call_from_apfl)
{
struct apfl_value func = apfl_stack_must_get(ctx, func_index);
must_tmproot_add_value(ctx, func);
struct apfl_value args = apfl_stack_must_get(ctx, args_index);
must_tmproot_add_value(ctx, args);
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){func_index, args_index}));
if (!VALUE_IS_A(func, APFL_VALUE_FUNC)) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_function);
}
if (!VALUE_IS_A(args, APFL_VALUE_LIST)) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
}
switch (func.type) {
case VALUE_FUNC:
prepare_call(ctx, tmproots, args, (struct call_stack_entry) {
.type = CSE_FUNCTION,
.stack = apfl_stack_new(),
.func = {
.pc = 0,
.instructions = func.func->body,
.scope = NULL,
.closure_scope = func.func->scope,
.execution_line = func.func->body->line,
},
});
if (call_from_apfl) {
// In this case we're already coming from evaluate_until_call_stack_return,
// which will pick up the new stack entry. This way we can avoid doing the recursion in C.
return;
} else {
evaluate_until_call_stack_return(ctx);
}
break;
case VALUE_CFUNC:
prepare_call(ctx, tmproots, args, (struct call_stack_entry) {
.type = CSE_CFUNCTION,
.stack = apfl_stack_new(),
.cfunc = {
.func = func.cfunc,
},
});
func.cfunc->func(ctx);
return_from_function(ctx);
break;
default:
assert(false); // Otherwise the VALUE_IS_A() check for APFL_VALUE_FUNC would have failed
}
}
static void
call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index, bool call_from_apfl)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
call_inner(ctx, tmproots, func_index, args_index, call_from_apfl);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
void
apfl_call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index)
{
call(ctx, func_index, args_index, false);
}
static void
evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
union instruction_or_arg arg;
assert(*i < ilist->len);
switch (ilist->instructions[(*i)++].instruction) {
case INSN_NIL:
apfl_push_nil(ctx);
return;
case INSN_TRUE:
apfl_push_bool(ctx, true);
return;
case INSN_FALSE:
apfl_push_bool(ctx, false);
return;
case INSN_NUMBER:
must_get_argument(ctx, i, ilist, &arg);
apfl_push_number(ctx, arg.number);
return;
case INSN_STRING:
must_get_argument(ctx, i, ilist, &arg);
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_STRING,
.string = arg.string,
});
return;
case INSN_LIST:
must_get_argument(ctx, i, ilist, &arg);
apfl_list_create(ctx, arg.count);
return;
case INSN_LIST_APPEND:
apfl_list_append(ctx, -2, -1);
return;
case INSN_LIST_EXPAND_INTO:
apfl_list_append_list(ctx, -2, -1);
return;
case INSN_DICT:
apfl_dict_create(ctx);
return;
case INSN_DICT_APPEND_KVPAIR:
apfl_dict_set(ctx, -3, -2, -1);
return;
case INSN_GET_MEMBER:
apfl_get_member(ctx, -2, -1);
return;
case INSN_VAR_NEW:
must_get_argument(ctx, i, ilist, &arg);
variable_new(ctx, arg.string);
return;
case INSN_VAR_GET:
must_get_argument(ctx, i, ilist, &arg);
variable_get(ctx, arg.string);
return;
case INSN_VAR_SET:
must_get_argument(ctx, i, ilist, &arg);
variable_set(ctx, arg.string, true);
return;
case INSN_NEXT_LINE:
ctx->execution_line++;
return;
case INSN_SET_LINE:
must_get_argument(ctx, i, ilist, &arg);
ctx->execution_line = arg.count;
return;
size_t *pc = &cse->pc;
struct instruction_list *ilist = cse->instructions;
while (*pc < cse->instructions->len) {
switch (ilist->instructions[(*pc)++].instruction) {
case INSN_NIL:
apfl_push_nil(ctx);
goto continue_loop;
case INSN_TRUE:
apfl_push_bool(ctx, true);
goto continue_loop;
case INSN_FALSE:
apfl_push_bool(ctx, false);
goto continue_loop;
case INSN_NUMBER:
must_get_argument(ctx, pc, ilist, &arg);
apfl_push_number(ctx, arg.number);
goto continue_loop;
case INSN_STRING:
must_get_argument(ctx, pc, ilist, &arg);
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_STRING,
.string = arg.string,
});
goto continue_loop;
case INSN_LIST:
must_get_argument(ctx, pc, ilist, &arg);
apfl_list_create(ctx, arg.count);
goto continue_loop;
case INSN_LIST_APPEND:
apfl_list_append(ctx, -2, -1);
goto continue_loop;
case INSN_LIST_EXPAND_INTO:
apfl_list_append_list(ctx, -2, -1);
goto continue_loop;
case INSN_DICT:
apfl_dict_create(ctx);
goto continue_loop;
case INSN_DICT_APPEND_KVPAIR:
apfl_dict_set(ctx, -3, -2, -1);
goto continue_loop;
case INSN_GET_MEMBER:
apfl_get_member(ctx, -2, -1);
goto continue_loop;
case INSN_VAR_NEW:
must_get_argument(ctx, pc, ilist, &arg);
variable_new(ctx, arg.string, false);
goto continue_loop;
case INSN_VAR_NEW_LOCAL:
must_get_argument(ctx, pc, ilist, &arg);
variable_new(ctx, arg.string, true);
goto continue_loop;
case INSN_VAR_GET:
must_get_argument(ctx, pc, ilist, &arg);
variable_get(ctx, arg.string);
goto continue_loop;
case INSN_VAR_SET:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, arg.string, true, false);
goto continue_loop;
case INSN_VAR_SET_LOCAL:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, arg.string, true, true);
goto continue_loop;
case INSN_MOVE_TO_LOCAL_VAR:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, arg.string, false, true);
goto continue_loop;
case INSN_NEXT_LINE:
cse->execution_line++;
goto continue_loop;
case INSN_SET_LINE:
must_get_argument(ctx, pc, ilist, &arg);
cse->execution_line = arg.count;
goto continue_loop;
case INSN_GET_BY_INDEX_KEEP:
must_get_argument(ctx, pc, ilist, &arg);
apfl_get_list_member_by_index(ctx, -1, arg.index);
goto continue_loop;
case INSN_DROP:
if (!apfl_stack_drop(ctx, -1)) {
apfl_raise_invalid_stackidx(ctx);
}
goto continue_loop;
case INSN_CALL:
call(ctx, -2, -1, true);
// By returning from this function, the newly pushed call stack entry (if any) will get picked up by
// evaluate_until_call_stack_return. In case no new CSE was pushed (when a cfunc was called), we'll the
// simply continue with the current call stack.
return;
case INSN_FUNC:
must_get_argument(ctx, pc, ilist, &arg);
func(ctx, arg.body);
goto continue_loop;
}
assert(false);
continue_loop:;
}
assert(false);
return_from_function(ctx);
}
static void
evaluate_list(apfl_ctx ctx, struct instruction_list *ilist)
{
ctx->execution_line = ilist->line;
size_t i = 0;
while (i < ilist->len) {
evaluate(ctx, &i, ilist);
}
}
struct apfl_iterative_runner_data {
apfl_ctx ctx;
apfl_tokenizer_ptr tokenizer;
apfl_parser_ptr parser;
enum apfl_result result;
bool with_error_on_stack;
bool end;
struct scope *scope;
};
static void
eval_expr_inner(apfl_ctx ctx, struct apfl_expr expr)
iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr expr)
{
apfl_ctx ctx = runner->ctx;
struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, expr.position.line);
if (ilist == NULL) {
apfl_raise_alloc_error(ctx);
@ -173,14 +549,26 @@ eval_expr_inner(apfl_ctx ctx, struct apfl_expr expr)
apfl_raise_error_object(ctx, error);
}
evaluate_list(ctx, ilist);
call_stack_push(ctx, (struct call_stack_entry) {
.type = CSE_FUNCTION,
.stack = apfl_stack_new(),
.func = {
.pc = 0,
.instructions = ilist,
.scope = runner->scope,
.closure_scope = NULL,
.execution_line = ilist->line,
},
});
evaluate_until_call_stack_return(ctx);
}
static void
eval_expr(apfl_ctx ctx, struct apfl_expr expr)
iterative_runner_eval_expr(apfl_iterative_runner runner, struct apfl_expr expr)
{
apfl_ctx ctx = runner->ctx;
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
eval_expr_inner(ctx, expr);
iterative_runner_eval_expr_inner(runner, expr);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
@ -198,34 +586,31 @@ apfl_debug_print_val(apfl_ctx ctx, apfl_stackidx index, struct apfl_format_write
return apfl_value_print(value, w);
}
struct apfl_iterative_runner_data {
apfl_ctx ctx;
apfl_tokenizer_ptr tokenizer;
apfl_parser_ptr parser;
enum apfl_result result;
bool with_error_on_stack;
bool end;
};
apfl_iterative_runner
apfl_iterative_runner_new(apfl_ctx ctx, struct apfl_source_reader reader)
{
apfl_iterative_runner runner = ALLOC_OBJ(ctx->gc.allocator, struct apfl_iterative_runner_data);
apfl_iterative_runner runner = NULL;
apfl_tokenizer_ptr tokenizer = NULL;
apfl_parser_ptr parser = NULL;
runner = ALLOC_OBJ(ctx->gc.allocator, struct apfl_iterative_runner_data);
if (runner == NULL) {
return NULL;
}
apfl_tokenizer_ptr tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader);
tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader);
if (tokenizer == NULL) {
FREE_OBJ(ctx->gc.allocator, runner);
return NULL;
goto error;
}
apfl_parser_ptr parser = apfl_parser_new(ctx->gc.allocator, apfl_tokenizer_as_token_source(tokenizer));
parser = apfl_parser_new(ctx->gc.allocator, apfl_tokenizer_as_token_source(tokenizer));
if (parser == NULL) {
FREE_OBJ(ctx->gc.allocator, runner);
apfl_tokenizer_destroy(tokenizer);
return NULL;
goto error;
}
struct scope *scope = apfl_scope_new(&ctx->gc);
if (scope == NULL) {
goto error;
}
*runner = (struct apfl_iterative_runner_data) {
@ -234,9 +619,21 @@ apfl_iterative_runner_new(apfl_ctx ctx, struct apfl_source_reader reader)
.parser = parser,
.result = APFL_RESULT_OK,
.end = false,
.scope = scope,
};
if (!apfl_ctx_register_iterative_runner(ctx, runner)) {
goto error;
}
return runner;
error:
FREE_OBJ(ctx->gc.allocator, runner);
apfl_tokenizer_destroy(tokenizer);
apfl_parser_destroy(parser);
return NULL;
}
static void
@ -265,13 +662,15 @@ handle_parse_error(apfl_ctx ctx, struct apfl_error error)
}
static void
iterative_runner_next_protected(apfl_ctx ctx, void *opaque)
iterative_runner_next_protected(apfl_ctx ctx)
{
apfl_iterative_runner runner = opaque;
apfl_stack_drop(ctx, -1);
apfl_cfunc_self_getslot(ctx, 0);
apfl_iterative_runner runner = apfl_get_userdata(ctx, -1);
switch (apfl_parser_next(runner->parser)) {
case APFL_PARSE_OK:
eval_expr(ctx, apfl_parser_get_expr(runner->parser));
iterative_runner_eval_expr(runner, apfl_parser_get_expr(runner->parser));
return;
case APFL_PARSE_ERROR:
handle_parse_error(runner->ctx, apfl_parser_get_error(runner->parser));
@ -293,13 +692,15 @@ apfl_iterative_runner_next(apfl_iterative_runner runner)
apfl_stack_clear(runner->ctx);
runner->with_error_on_stack = false;
runner->result = apfl_protected(
runner->ctx,
iterative_runner_next_protected,
runner,
&runner->with_error_on_stack
);
// TODO: It's a bit silly that we build the cfunc every time. Can we be
// smarter and leave it on the stack somewhere? Or save it in the
// runner? Also, what if the construction of the func fails? We're not
// running protected yet :/
apfl_push_cfunc(runner->ctx, iterative_runner_next_protected, 1);
apfl_push_userdata(runner->ctx, runner);
apfl_cfunc_setslot(runner->ctx, -2, 0, -1);
apfl_list_create(runner->ctx, 0);
apfl_call_protected(runner->ctx, -2, -1, &runner->with_error_on_stack);
return !runner->end;
}
@ -325,5 +726,20 @@ apfl_iterative_runner_destroy(apfl_iterative_runner runner)
apfl_parser_destroy(runner->parser);
apfl_tokenizer_destroy(runner->tokenizer);
apfl_ctx_unregister_iterative_runner(runner->ctx, runner);
FREE_OBJ(runner->ctx->gc.allocator, runner);
}
void
apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner runner, gc_visitor visitor, void *opaque)
{
// TODO: It's a bit awkward that this function is defined here but the
// prototype lives in context.h... Maybe we should just merge context
// and eval together? The separation is rather arbitrary anyway :/
if (runner->scope != NULL) {
visitor(opaque, GC_OBJECT_FROM(runner->scope, GC_TYPE_SCOPE));
}
}

View file

@ -23,6 +23,8 @@ struct gc_object {
struct instruction_list instructions;
struct scope scope;
struct stack stack;
struct function function;
struct cfunction cfunction;
};
enum gc_type type;
enum gc_status status;
@ -157,6 +159,8 @@ IMPL_NEW(struct apfl_value, apfl_gc_new_var, GC_T
IMPL_NEW(struct apfl_string, apfl_gc_new_string, GC_TYPE_STRING, string )
IMPL_NEW(struct instruction_list, apfl_gc_new_instructions, GC_TYPE_INSTRUCTIONS, instructions )
IMPL_NEW(struct scope, apfl_gc_new_scope, GC_TYPE_SCOPE, scope )
IMPL_NEW(struct function, apfl_gc_new_func, GC_TYPE_FUNC, function )
IMPL_NEW(struct cfunction, apfl_gc_new_cfunc, GC_TYPE_CFUNC, cfunction )
size_t
apfl_gc_tmproots_begin(struct gc *gc)
@ -250,6 +254,12 @@ visit_children(struct gc_object *object, gc_visitor cb, void *opaque)
case GC_TYPE_INSTRUCTIONS:
apfl_gc_instructions_traverse(&object->instructions, cb, opaque);
return;
case GC_TYPE_FUNC:
apfl_gc_func_traverse(&object->function, cb, opaque);
return;
case GC_TYPE_CFUNC:
apfl_gc_cfunc_traverse(&object->cfunction, cb, opaque);
return;
}
assert(false);
@ -312,6 +322,12 @@ deinit_object(struct gc *gc, struct gc_object *object)
case GC_TYPE_SCOPE:
apfl_scope_deinit(gc->allocator, &object->scope);
return;
case GC_TYPE_FUNC:
apfl_function_deinit(gc->allocator, &object->function);
return;
case GC_TYPE_CFUNC:
apfl_cfunction_deinit(gc->allocator, &object->cfunction);
return;
}
assert(false);
@ -405,6 +421,10 @@ type_to_string(enum gc_type type)
return "instructions";
case GC_TYPE_SCOPE:
return "scope";
case GC_TYPE_FUNC:
return "func";
case GC_TYPE_CFUNC:
return "cfunc";
}
assert(false);

View file

@ -27,6 +27,8 @@ enum gc_type {
GC_TYPE_STRING,
GC_TYPE_INSTRUCTIONS,
GC_TYPE_SCOPE,
GC_TYPE_FUNC,
GC_TYPE_CFUNC,
};
struct gc_tmproots {
@ -76,6 +78,8 @@ struct apfl_value* apfl_gc_new_var(struct gc *);
struct apfl_string* apfl_gc_new_string(struct gc *);
struct instruction_list* apfl_gc_new_instructions(struct gc *);
struct scope* apfl_gc_new_scope(struct gc *);
struct function* apfl_gc_new_func(struct gc *);
struct cfunction* apfl_gc_new_cfunc(struct gc *);
#ifdef __cplusplus
}

View file

@ -13,4 +13,8 @@ const struct apfl_messages apfl_messages = {
.value_is_not_a_container = "Value is not a container (list or dictionary)",
.wrong_key_type = "Wrong key type",
.variable_doesnt_exist = "Variable does not exist",
.not_a_c_function = "Not a C function",
.invalid_slotidx = "Invalid slot index",
.not_a_function = "Not a function",
.wrong_type = "Wrong type",
};

View file

@ -101,6 +101,12 @@ apfl_scope_get(struct scope *scope, struct apfl_string *name, struct apfl_value
return true;
}
bool
apfl_scope_has(struct scope *scope, struct apfl_string *name)
{
return apfl_hashmap_isset(&scope->map, &name);
}
static variable
get_or_create_variable(struct gc *gc, struct scope *scope, struct apfl_string *name)
{
@ -137,8 +143,42 @@ apfl_scope_set(struct gc *gc, struct scope *scope, struct apfl_string *name, str
return true;
}
bool
apfl_scope_update_existing(struct scope *scope, struct apfl_string *name, struct apfl_value value)
{
variable var;
if (!apfl_hashmap_get(&scope->map, &name, &var)) {
return false;
}
*var = value;
return true;
}
bool
apfl_scope_create_var(struct gc *gc, struct scope *scope, struct apfl_string *name)
{
return get_or_create_variable(gc, scope, name) != NULL;
}
bool
apfl_scope_merge_into(struct scope *self, struct scope *other)
{
if (self == NULL || other == NULL) {
return NULL;
}
HASHMAP_EACH(&other->map, cur) {
struct apfl_string **k = apfl_hashmap_cursor_peek_key(cur);
assert(k != NULL);
variable *v = apfl_hashmap_cursor_peek_value(cur);
assert(v != NULL);
if (!apfl_hashmap_set(&self->map, k, v)) {
return false;
}
}
return true;
}

View file

@ -17,8 +17,12 @@ struct scope *apfl_scope_new(struct gc *);
void apfl_scope_deinit(struct apfl_allocator, struct scope *);
bool apfl_scope_get(struct scope *, struct apfl_string *name, struct apfl_value *out);
bool apfl_scope_has(struct scope *, struct apfl_string *name);
bool apfl_scope_set(struct gc *, struct scope *, struct apfl_string *name, struct apfl_value value);
bool apfl_scope_create_var(struct gc *, struct scope *, struct apfl_string *name);
bool apfl_scope_update_existing(struct scope *, struct apfl_string *, struct apfl_value);
bool apfl_scope_merge_into(struct scope *self, struct scope *other);
void apfl_gc_var_traverse(struct apfl_value *, gc_visitor, void *);
void apfl_gc_scope_traverse(struct scope *, gc_visitor, void *);

View file

@ -142,6 +142,15 @@ format(unsigned indent, struct apfl_format_writer w, struct apfl_value value, bo
TRY(apfl_format_put_indent(w, indent));
TRY(apfl_format_put_string(w, "]"));
return true;
case VALUE_FUNC:
TRY(apfl_format_put_string(w, "{ ... }"));
return true;
case VALUE_CFUNC:
TRY(apfl_format_put_string(w, "{ native code }"));
return true;
case VALUE_USERDATA:
TRY(apfl_format_put_string(w, "userdata"));
return true;
}
TRY(apfl_format_put_string(w, "Unknown value ? ("));
@ -150,6 +159,90 @@ format(unsigned indent, struct apfl_format_writer w, struct apfl_value value, bo
return true;
}
enum apfl_value_type
apfl_value_type_to_abstract_type(enum value_type type)
{
switch (type) {
case VALUE_NIL:
return APFL_VALUE_NIL;
case VALUE_BOOLEAN:
return APFL_VALUE_BOOLEAN;
case VALUE_NUMBER:
return APFL_VALUE_NUMBER;
case VALUE_STRING:
case VALUE_CONST_STRING:
return APFL_VALUE_STRING;
case VALUE_LIST:
return APFL_VALUE_LIST;
case VALUE_DICT:
return APFL_VALUE_DICT;
case VALUE_FUNC:
case VALUE_CFUNC:
return APFL_VALUE_FUNC;
case VALUE_USERDATA:
return APFL_VALUE_USERDATA;
}
assert(false);
return 0;
}
struct function *
apfl_func_new(struct gc *gc, struct instruction_list *body, struct scope *scope)
{
struct function *function = apfl_gc_new_func(gc);
if (function == NULL) {
return NULL;
}
*function = (struct function) {
.body = body,
.scope = scope,
};
return function;
}
void
apfl_function_deinit(struct apfl_allocator allocator, struct function *function)
{
(void)allocator;
(void)function;
}
struct cfunction *
apfl_cfunc_new(struct gc *gc, apfl_cfunc func, size_t nslots)
{
struct apfl_value **slots = NULL;
if (nslots > 0) {
slots = ALLOC_LIST(gc->allocator, struct apfl_value *, nslots);
if (slots == NULL) {
return NULL;
}
}
struct cfunction *cfunction = apfl_gc_new_cfunc(gc);
if (cfunction == NULL) {
FREE_LIST(gc->allocator, slots, nslots);
}
for (size_t i = 0; i < nslots; i++) {
slots[i] = NULL;
}
cfunction->func = func;
cfunction->slots = slots;
cfunction->slots_len = nslots;
return cfunction;
}
void
apfl_cfunction_deinit(struct apfl_allocator allocator, struct cfunction *cfunc)
{
FREE_LIST(allocator, cfunc->slots, cfunc->slots_len);
}
struct apfl_value
apfl_value_move(struct apfl_value *src)
{
@ -233,6 +326,12 @@ apfl_value_eq(const struct apfl_value a, const struct apfl_value b)
return b.type == VALUE_LIST && list_eq(a.list, b.list);
case VALUE_DICT:
return b.type == VALUE_DICT && dict_eq(a.dict, b.dict);
case VALUE_FUNC:
return b.type == VALUE_FUNC && a.func == b.func;
case VALUE_CFUNC:
return b.type == VALUE_CFUNC && a.cfunc == b.cfunc;
case VALUE_USERDATA:
return b.type == VALUE_USERDATA && a.userdata == b.userdata;
}
assert(false);
@ -488,6 +587,12 @@ apfl_value_hash(const struct apfl_value value)
// it's rather unusual to have dictionaries as keys, this is fine
// for now, but should be improved nonetheless!
return hash;
case VALUE_FUNC:
return apfl_hash_fnv1a_add(&value.func, sizeof(struct function *), hash);
case VALUE_CFUNC:
return apfl_hash_fnv1a_add(&value.cfunc, sizeof(struct cfunction *), hash);
case VALUE_USERDATA:
return apfl_hash_fnv1a_add(&value.userdata, sizeof(void *), hash);
}
assert(false);
@ -503,6 +608,7 @@ apfl_value_get_gc_object(struct apfl_value value)
case VALUE_BOOLEAN:
case VALUE_NUMBER:
case VALUE_CONST_STRING:
case VALUE_USERDATA:
return NULL;
case VALUE_STRING:
return GC_OBJECT_FROM(value.string, GC_TYPE_STRING);
@ -510,6 +616,10 @@ apfl_value_get_gc_object(struct apfl_value value)
return GC_OBJECT_FROM(value.list, GC_TYPE_LIST);
case VALUE_DICT:
return GC_OBJECT_FROM(value.dict, GC_TYPE_DICT);
case VALUE_FUNC:
return GC_OBJECT_FROM(value.func, GC_TYPE_FUNC);
case VALUE_CFUNC:
return GC_OBJECT_FROM(value.cfunc, GC_TYPE_CFUNC);
}
assert(false);
@ -547,3 +657,19 @@ apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque)
}
}
void
apfl_gc_func_traverse(struct function* function, gc_visitor cb, void *opaque)
{
cb(opaque, GC_OBJECT_FROM(function->body, GC_TYPE_INSTRUCTIONS));
cb(opaque, GC_OBJECT_FROM(function->scope, GC_TYPE_SCOPE));
}
void
apfl_gc_cfunc_traverse(struct cfunction* cfunc, gc_visitor cb, void *opaque)
{
for (size_t i = 0; i < cfunc->slots_len; i++) {
if (cfunc->slots[i] != NULL) {
cb(opaque, GC_OBJECT_FROM(cfunc->slots[i], GC_TYPE_VAR));
}
}
}

View file

@ -11,6 +11,7 @@ extern "C" {
#include "apfl.h"
#include "bytecode.h"
#include "gc.h"
#include "hashmap.h"
@ -23,7 +24,9 @@ enum value_type {
VALUE_CONST_STRING,
VALUE_LIST,
VALUE_DICT,
// TODO: functions/closures
VALUE_FUNC,
VALUE_CFUNC,
VALUE_USERDATA,
};
struct list_header {
@ -38,6 +41,17 @@ struct dict_header {
bool copy_on_write;
};
struct function {
struct instruction_list *body;
struct scope *scope;
};
struct cfunction {
apfl_cfunc func;
struct apfl_value **slots;
size_t slots_len;
};
typedef struct dict_header *apfl_dict;
struct apfl_value {
@ -49,6 +63,9 @@ struct apfl_value {
struct apfl_string_view const_string;
struct list_header *list;
struct dict_header *dict;
struct function *func;
struct cfunction *cfunc;
void *userdata;
};
};
@ -98,10 +115,18 @@ bool apfl_dict_set_raw(
);
void apfl_dict_deinit(struct dict_header *);
struct function *apfl_func_new(struct gc *, struct instruction_list *, struct scope *);
void apfl_function_deinit(struct apfl_allocator, struct function *);
struct cfunction *apfl_cfunc_new(struct gc *, apfl_cfunc, size_t nslots);
void apfl_cfunction_deinit(struct apfl_allocator, struct cfunction *);
// Functions for garbage collection
struct gc_object *apfl_value_get_gc_object(struct apfl_value);
void apfl_gc_list_traverse(struct list_header *, gc_visitor, void *);
void apfl_gc_dict_traverse(struct dict_header *, gc_visitor, void *);
void apfl_gc_func_traverse(struct function*, gc_visitor, void *);
void apfl_gc_cfunc_traverse(struct cfunction*, gc_visitor, void *);
#ifdef __cplusplus
}