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:
parent
eb7cb0baf7
commit
a4f7f0f2ff
14 changed files with 1522 additions and 198 deletions
24
src/apfl.h
24
src/apfl.h
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 "??";
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
161
src/compile.c
161
src/compile.c
|
|
@ -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, ¶m->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:
|
||||
|
|
|
|||
549
src/context.c
549
src/context.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
652
src/eval.c
652
src/eval.c
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/gc.c
20
src/gc.c
|
|
@ -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);
|
||||
|
|
|
|||
4
src/gc.h
4
src/gc.h
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
40
src/scope.c
40
src/scope.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
|
|
|||
126
src/value.c
126
src/value.c
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
src/value.h
27
src/value.h
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue