2169 lines
55 KiB
C
2169 lines
55 KiB
C
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <setjmp.h>
|
|
|
|
#include "apfl.h"
|
|
#include "alloc.h"
|
|
#include "compile.h"
|
|
#include "context.h"
|
|
#include "gc.h"
|
|
#include "globals.h"
|
|
#include "hashmap.h"
|
|
#include "resizable.h"
|
|
#include "strings.h"
|
|
#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);
|
|
static struct apfl_string *new_copied_string(struct gc *, struct apfl_string_view);
|
|
|
|
noreturn static void
|
|
panic(apfl_ctx ctx, enum apfl_result result)
|
|
{
|
|
(void)ctx;
|
|
(void)result;
|
|
fprintf(stderr, "panic!\n");
|
|
// TODO: more details
|
|
abort();
|
|
}
|
|
|
|
struct protected_errcallback_data {
|
|
void *opaque_outer;
|
|
void (*errcallback)(apfl_ctx, void *);
|
|
};
|
|
|
|
void
|
|
protected_errcallback(apfl_ctx ctx, void *opaque)
|
|
{
|
|
struct protected_errcallback_data *data = opaque;
|
|
data->errcallback(ctx, data->opaque_outer);
|
|
}
|
|
|
|
enum apfl_result
|
|
apfl_do_protected(
|
|
apfl_ctx ctx,
|
|
void (*callback)(apfl_ctx, void *),
|
|
void *opaque,
|
|
void (*errcallback)(apfl_ctx, void *)
|
|
) {
|
|
struct error_handler *prev_handler = ctx->error_handler;
|
|
|
|
size_t callstack_len = ctx->call_stack.len;
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
struct error_handler handler;
|
|
ctx->error_handler = &handler;
|
|
|
|
enum apfl_result result = APFL_RESULT_OK;
|
|
int rv = setjmp(handler.jump);
|
|
if (rv == 0) {
|
|
callback(ctx, opaque);
|
|
} else {
|
|
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
|
|
assert(result != APFL_RESULT_OK);
|
|
|
|
bool with_error_on_stack = APFL_RESULT_ERR;
|
|
|
|
if (with_error_on_stack && errcallback != NULL) {
|
|
struct protected_errcallback_data data = {
|
|
.opaque_outer = opaque,
|
|
.errcallback = errcallback,
|
|
};
|
|
switch (apfl_do_protected(ctx, protected_errcallback, &data, NULL)) {
|
|
case APFL_RESULT_OK:
|
|
break;
|
|
case APFL_RESULT_ERR:
|
|
result = APFL_RESULT_ERRERR;
|
|
with_error_on_stack = false;
|
|
break;
|
|
case APFL_RESULT_ERRERR:
|
|
result = APFL_RESULT_ERRERR;
|
|
with_error_on_stack = false;
|
|
break;
|
|
case APFL_RESULT_ERR_ALLOC:
|
|
result = APFL_RESULT_ERR_ALLOC;
|
|
with_error_on_stack = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct apfl_value err;
|
|
if (with_error_on_stack && !apfl_stack_pop(ctx, &err, -1)) {
|
|
result = APFL_RESULT_ERRERR;
|
|
with_error_on_stack = false;
|
|
}
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
if (with_error_on_stack && !apfl_value_add_as_tmproot(&ctx->gc, err)) {
|
|
result = APFL_RESULT_ERRERR;
|
|
with_error_on_stack = false;
|
|
}
|
|
|
|
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 && !apfl_stack_push(ctx, err)) {
|
|
result = APFL_RESULT_ERRERR;
|
|
with_error_on_stack = false;
|
|
}
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
}
|
|
|
|
ctx->error_handler = prev_handler;
|
|
|
|
return result;
|
|
}
|
|
|
|
struct call_protected_data {
|
|
apfl_stackidx func;
|
|
apfl_stackidx args;
|
|
};
|
|
|
|
static void
|
|
call_protected_cb(apfl_ctx ctx, void *opaque)
|
|
{
|
|
struct call_protected_data *data = opaque;
|
|
apfl_call(ctx, data->func, data->args);
|
|
}
|
|
|
|
enum apfl_result
|
|
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args)
|
|
{
|
|
struct call_protected_data data = {
|
|
.func = func,
|
|
.args = args,
|
|
};
|
|
return apfl_do_protected(ctx, call_protected_cb, &data, NULL);
|
|
}
|
|
|
|
noreturn static void
|
|
raise_error(apfl_ctx ctx, enum apfl_result type)
|
|
{
|
|
assert(type != APFL_RESULT_OK);
|
|
|
|
if (ctx->error_handler != NULL) {
|
|
longjmp(ctx->error_handler->jump, (int)type + RESULT_OFF_FOR_LONGJMP);
|
|
}
|
|
|
|
if (ctx->panic_callback != NULL) {
|
|
ctx->panic_callback(ctx, ctx->panic_callback_data, type);
|
|
}
|
|
|
|
panic(ctx, type);
|
|
}
|
|
|
|
noreturn void
|
|
apfl_raise_error(apfl_ctx ctx, apfl_stackidx idx)
|
|
{
|
|
if (!current_stack_move_to_top(ctx, idx)) {
|
|
raise_error(ctx, APFL_RESULT_ERRERR);
|
|
}
|
|
raise_error(ctx, APFL_RESULT_ERR);
|
|
}
|
|
|
|
noreturn void
|
|
apfl_raise_const_error(apfl_ctx ctx, const char *message)
|
|
{
|
|
if (!try_push_const_string(ctx, message)) {
|
|
raise_error(ctx, APFL_RESULT_ERRERR);
|
|
}
|
|
raise_error(ctx, APFL_RESULT_ERR);
|
|
}
|
|
|
|
noreturn void
|
|
apfl_raise_alloc_error(apfl_ctx ctx)
|
|
{
|
|
raise_error(ctx, APFL_RESULT_ERR_ALLOC);
|
|
}
|
|
|
|
noreturn void
|
|
apfl_raise_invalid_stackidx(apfl_ctx ctx)
|
|
{
|
|
apfl_raise_const_error(ctx, apfl_messages.invalid_stack_index);
|
|
}
|
|
|
|
noreturn void
|
|
apfl_raise_error_object(apfl_ctx ctx, struct apfl_error error)
|
|
{
|
|
if (error.type == APFL_ERR_MALLOC_FAILED) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
const char *const_str = apfl_error_as_const_string(error);
|
|
if (const_str != NULL) {
|
|
apfl_raise_const_error(ctx, const_str);
|
|
}
|
|
|
|
struct apfl_string string;
|
|
if (!apfl_error_as_string(error, ctx->gc.allocator, &string)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
if (!apfl_move_string_onto_stack(ctx, string)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
apfl_raise_error(ctx, -1);
|
|
}
|
|
|
|
struct errorfmt_data {
|
|
va_list ap;
|
|
apfl_ctx ctx;
|
|
};
|
|
|
|
static bool
|
|
errorfmt_str(void *opaque, struct apfl_io_writer w, struct apfl_string_view arg)
|
|
{
|
|
struct errorfmt_data *data = opaque;
|
|
|
|
struct apfl_string_view sv;
|
|
if (apfl_string_eq(arg, "c")) {
|
|
char *cstr = va_arg(data->ap, char *);
|
|
sv = apfl_string_view_from(cstr);
|
|
} else {
|
|
sv = va_arg(data->ap, struct apfl_string_view);
|
|
}
|
|
|
|
return apfl_io_write_string_view(w, sv);
|
|
}
|
|
|
|
static bool
|
|
errorfmt_type(void *opaque, struct apfl_io_writer w, struct apfl_string_view arg)
|
|
{
|
|
struct errorfmt_data *data = opaque;
|
|
|
|
enum apfl_value_type type;
|
|
if (apfl_string_eq(arg, "stack")) {
|
|
apfl_stackidx i = va_arg(data->ap, apfl_stackidx);
|
|
struct apfl_value val;
|
|
if (!apfl_stack_get(data->ctx, &val, i)) {
|
|
FMT_TRY(apfl_io_write_string(w, "{stack:type => invalid stack index}"));
|
|
return true;
|
|
}
|
|
type = apfl_value_type_to_abstract_type(val.type);
|
|
} else if (apfl_string_eq(arg, "value")) {
|
|
struct apfl_value val = va_arg(data->ap, struct apfl_value);
|
|
type = apfl_value_type_to_abstract_type(val.type);
|
|
} else {
|
|
type = va_arg(data->ap, enum apfl_value_type);
|
|
}
|
|
|
|
return apfl_io_write_string_view(w, apfl_string_view_from(apfl_type_name(type)));
|
|
}
|
|
|
|
static const struct format_spec errorfmt_specs[] = {
|
|
{"string", errorfmt_str},
|
|
{"type", errorfmt_type},
|
|
{NULL, NULL},
|
|
};
|
|
|
|
noreturn void
|
|
apfl_raise_errorfmt(apfl_ctx ctx, const char *fmt, ...)
|
|
{
|
|
struct errorfmt_data data = {
|
|
.ctx = ctx,
|
|
};
|
|
va_start(data.ap, fmt);
|
|
|
|
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
|
|
struct apfl_io_writer w = apfl_io_string_writer(&sb);
|
|
|
|
bool result = apfl_abstract_format(&data, errorfmt_specs, w, apfl_string_view_from(fmt));
|
|
va_end(data.ap);
|
|
|
|
if (!result) {
|
|
apfl_string_builder_deinit(&sb);
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
struct apfl_string string = apfl_string_builder_move_string(&sb);
|
|
apfl_string_builder_deinit(&sb);
|
|
|
|
if (!apfl_move_string_onto_stack(ctx, string)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
apfl_raise_error(ctx, -1);
|
|
}
|
|
|
|
void
|
|
apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque)
|
|
{
|
|
ctx->panic_callback = cb;
|
|
ctx->panic_callback_data = opaque;
|
|
}
|
|
|
|
struct stack
|
|
apfl_stack_new(void)
|
|
{
|
|
return (struct stack) {
|
|
.items = NULL,
|
|
.len = 0,
|
|
.cap = 0,
|
|
};
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!apfl_stack_push(ctx, value)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
}
|
|
|
|
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 **)&stack->items,
|
|
&stack->len,
|
|
&stack->cap,
|
|
&value,
|
|
1
|
|
);
|
|
}
|
|
|
|
static bool
|
|
stack_check_index(struct stack *stack, apfl_stackidx *index)
|
|
{
|
|
if (*index < 0) {
|
|
if ((size_t)-*index > stack->len) {
|
|
return false;
|
|
}
|
|
*index = stack->len + *index;
|
|
} else if ((size_t)*index >= stack->len) {
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
{
|
|
const apfl_stackidx *a = _a;
|
|
const apfl_stackidx *b = _b;
|
|
return *a - *b;
|
|
}
|
|
|
|
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 (!stack_check_index(stack, &indices[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qsort(indices, count, sizeof(apfl_stackidx), cmp_stackidx);
|
|
|
|
for (size_t i = count; i-- > 0; ) {
|
|
// Will not fail, as we've already checked the indices
|
|
assert(apfl_resizable_cut_without_resize(
|
|
sizeof(struct apfl_value),
|
|
(void **)&stack->items,
|
|
&stack->len,
|
|
indices[i],
|
|
1
|
|
));
|
|
}
|
|
|
|
// TODO: Shrink stack
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
{
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
if (!stack_check_index(stack, &index)) {
|
|
return false;
|
|
}
|
|
|
|
*value = stack->items[index];
|
|
|
|
assert(apfl_resizable_splice(
|
|
ctx->gc.allocator,
|
|
sizeof(struct apfl_value),
|
|
(void **)&stack->items,
|
|
&stack->len,
|
|
&stack->cap,
|
|
index,
|
|
1,
|
|
NULL,
|
|
0
|
|
));
|
|
|
|
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)
|
|
{
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
if (!stack_check_index(stack, &index)) {
|
|
return NULL;
|
|
}
|
|
|
|
return &stack->items[index];
|
|
}
|
|
|
|
static bool
|
|
stack_get_and_adjust_index(struct stack *stack, struct apfl_value *value, apfl_stackidx *index)
|
|
{
|
|
if (!stack_check_index(stack, index)) {
|
|
return false;
|
|
}
|
|
|
|
*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 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 *
|
|
apfl_stack_push_placeholder(apfl_ctx ctx)
|
|
{
|
|
if (!apfl_stack_push(ctx, (struct apfl_value) {.type = VALUE_NIL})) {
|
|
return NULL;
|
|
}
|
|
|
|
return stack_get_pointer(ctx, -1);
|
|
}
|
|
|
|
bool
|
|
apfl_move_string_onto_stack(apfl_ctx ctx, struct apfl_string string)
|
|
{
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
if (value == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if ((value->string = apfl_string_move_into_new_gc_string(&ctx->gc, &string)) == NULL) {
|
|
return false;
|
|
}
|
|
|
|
value->type = VALUE_STRING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value;
|
|
return apfl_stack_pop(ctx, &value, index);
|
|
}
|
|
|
|
void
|
|
apfl_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
if (!apfl_stack_drop(ctx, index)) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
}
|
|
|
|
bool
|
|
current_stack_multi_move_to_top(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 (!stack_check_index(stack, &indices[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
// If we're here, index is an absolute address and is guaranteed to be < len
|
|
assert(indices[i] >= 0);
|
|
size_t absindex = (size_t)indices[i];
|
|
assert(stack->len >= absindex+1);
|
|
|
|
struct apfl_value val = stack->items[absindex];
|
|
|
|
memmove(
|
|
&stack->items[absindex],
|
|
&stack->items[absindex + 1],
|
|
sizeof(struct apfl_value) * (stack->len - absindex - 1)
|
|
);
|
|
stack->items[stack->len-1] = val;
|
|
|
|
for (size_t j = i + 1; j < count; j++) {
|
|
if (indices[j] >= indices[i]) {
|
|
indices[j]--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
return current_stack_multi_move_to_top(ctx, 1, &index);
|
|
}
|
|
|
|
void
|
|
apfl_move_to_top_of_stack(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
if (!current_stack_move_to_top(ctx, index)) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
}
|
|
|
|
void
|
|
apfl_multi_move_to_top_of_stack(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
|
{
|
|
if (!current_stack_multi_move_to_top(ctx, count, indices)) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
}
|
|
|
|
void
|
|
apfl_copy(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value));
|
|
}
|
|
|
|
void
|
|
apfl_stack_clear(apfl_ctx ctx)
|
|
{
|
|
struct stack *stack = current_value_stack(ctx);
|
|
stack->len = 0;
|
|
}
|
|
|
|
static void
|
|
stack_traverse(struct stack stack, gc_visitor visitor, void *opaque)
|
|
{
|
|
for (size_t i = 0; i < stack.len; i++) {
|
|
apfl_value_visit_gc_object(stack.items[i], visitor, opaque);
|
|
}
|
|
}
|
|
|
|
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
|
|
visit_scopes(struct scopes scopes, gc_visitor visitor, void *opaque)
|
|
{
|
|
visit_nullable_scope(scopes.local, visitor, opaque);
|
|
visit_nullable_scope(scopes.closure, visitor, opaque);
|
|
}
|
|
|
|
static void
|
|
visit_matcher_stack(struct matcher_stack matcher_stack, gc_visitor visitor, void *opaque)
|
|
{
|
|
for (size_t i = 0; i < matcher_stack.len; i++) {
|
|
visitor(
|
|
opaque,
|
|
GC_OBJECT_FROM(matcher_stack.items[i], GC_TYPE_MATCHER)
|
|
);
|
|
}
|
|
}
|
|
|
|
static void
|
|
visit_func_cse(struct func_call_stack_entry cse, gc_visitor visitor, void *opaque)
|
|
{
|
|
visitor(
|
|
opaque,
|
|
GC_OBJECT_FROM(cse.instructions, GC_TYPE_INSTRUCTIONS)
|
|
);
|
|
|
|
visit_scopes(cse.scopes, visitor, opaque);
|
|
visit_matcher_stack(cse.matcher_stack, visitor, opaque);
|
|
}
|
|
|
|
static void
|
|
visit_cfunc_cse(struct cfunc_call_stack_entry cse, gc_visitor visitor, void *opaque)
|
|
{
|
|
visitor(
|
|
opaque,
|
|
GC_OBJECT_FROM(cse.func, GC_TYPE_CFUNC)
|
|
);
|
|
}
|
|
|
|
static void
|
|
visit_matcher_cse(struct matcher_call_stack_entry cse, gc_visitor visitor, void* opaque)
|
|
{
|
|
visitor(
|
|
opaque,
|
|
GC_OBJECT_FROM(cse.matcher, GC_TYPE_MATCHER)
|
|
);
|
|
visit_scopes(cse.scopes, visitor, opaque);
|
|
for (size_t i = 0; i < cse.capture_count; i++) {
|
|
apfl_value_visit_gc_object(cse.captures[i], visitor, opaque);
|
|
visitor(
|
|
opaque,
|
|
GC_OBJECT_FROM(cse.transfers[i].var, GC_TYPE_STRING)
|
|
);
|
|
}
|
|
}
|
|
|
|
static void
|
|
visit_func_dispatch_cse(struct func_dispatch_call_stack_entry cse, gc_visitor visitor, void *opaque)
|
|
{
|
|
visitor(
|
|
opaque,
|
|
GC_OBJECT_FROM(cse.function, GC_TYPE_FUNC)
|
|
);
|
|
visit_scopes(cse.scopes, visitor, opaque);
|
|
}
|
|
|
|
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 APFL_CSE_FUNCTION:
|
|
visit_func_cse(cse.func, visitor, opaque);
|
|
break;
|
|
case APFL_CSE_CFUNCTION:
|
|
visit_cfunc_cse(cse.cfunc, visitor, opaque);
|
|
break;
|
|
case APFL_CSE_MATCHER:
|
|
visit_matcher_cse(cse.matcher, visitor, opaque);
|
|
break;
|
|
case APFL_CSE_FUNCTION_DISPATCH:
|
|
visit_func_dispatch_cse(cse.func_dispatch, visitor, opaque);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
|
|
{
|
|
apfl_ctx ctx = own_opaque;
|
|
|
|
if (ctx->globals != NULL) {
|
|
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->globals, GC_TYPE_SCOPE));
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
static void
|
|
func_call_stack_entry_deinit(struct apfl_allocator allocator, struct func_call_stack_entry *cse)
|
|
{
|
|
FREE_LIST(allocator, cse->matcher_stack.items, cse->matcher_stack.cap);
|
|
}
|
|
|
|
void
|
|
apfl_matcher_call_stack_entry_deinit(struct apfl_allocator allocator, struct matcher_call_stack_entry *cse)
|
|
{
|
|
FREE_LIST(allocator, cse->captures, cse->capture_count);
|
|
cse->captures = NULL;
|
|
FREE_LIST(allocator, cse->transfers, cse->capture_count);
|
|
cse->transfers = NULL;
|
|
FREE_LIST(allocator, cse->matcher_state_stack, cse->matcher_state_stack_cap);
|
|
cse->matcher_state_stack = NULL;
|
|
}
|
|
|
|
void
|
|
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
|
|
{
|
|
deinit_stack(allocator, &entry->stack);
|
|
|
|
switch (entry->type) {
|
|
case APFL_CSE_FUNCTION:
|
|
func_call_stack_entry_deinit(allocator, &entry->func);
|
|
break;
|
|
case APFL_CSE_CFUNCTION:
|
|
case APFL_CSE_FUNCTION_DISPATCH:
|
|
break;
|
|
case APFL_CSE_MATCHER:
|
|
apfl_matcher_call_stack_entry_deinit(allocator, &entry->matcher);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
static bool
|
|
init_globals(apfl_ctx ctx)
|
|
{
|
|
struct gc *gc = &ctx->gc;
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(gc);
|
|
|
|
for (
|
|
const struct global_def *global = apfl_globals();
|
|
global->name != NULL && global->func != NULL;
|
|
global++
|
|
) {
|
|
struct cfunction *cfunc = apfl_cfunc_new(gc, global->func, 0);
|
|
if (cfunc == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(cfunc, GC_TYPE_CFUNC))) {
|
|
goto error;
|
|
}
|
|
|
|
struct apfl_string *name = new_copied_string(gc, apfl_string_view_from(global->name));
|
|
if (name == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(name, GC_TYPE_STRING))) {
|
|
goto error;
|
|
}
|
|
|
|
// TODO: There should also be an API for setting the name of a cfunc
|
|
cfunc->name = name;
|
|
|
|
if (!apfl_scope_set(gc, ctx->globals, name, (struct apfl_value) {
|
|
.type = VALUE_CFUNC,
|
|
.cfunc = cfunc,
|
|
})) {
|
|
goto error;
|
|
}
|
|
|
|
apfl_gc_tmproots_restore(gc, tmproots);
|
|
}
|
|
|
|
return true;
|
|
|
|
error:
|
|
apfl_gc_tmproots_restore(gc, tmproots);
|
|
return false;
|
|
}
|
|
|
|
apfl_ctx
|
|
apfl_ctx_new(struct apfl_config config)
|
|
{
|
|
apfl_ctx ctx = ALLOC_OBJ(config.allocator, struct apfl_ctx_data);
|
|
if (ctx == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// It's important that we initialize all these before initializing the
|
|
// garbage collector, otherwise it might try to follow invalid pointers.
|
|
ctx->error_handler = NULL;
|
|
ctx->panic_callback = NULL;
|
|
ctx->panic_callback_data = NULL;
|
|
ctx->toplevel_stack = apfl_stack_new();
|
|
ctx->call_stack = call_stack_new();
|
|
ctx->globals = NULL;
|
|
ctx->iterative_runners = iterative_runners_list_new();
|
|
|
|
apfl_gc_init(&ctx->gc, config.allocator, get_roots, ctx);
|
|
|
|
if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
ctx->output_writer = config.output_writer;
|
|
|
|
if (!init_globals(ctx)) {
|
|
goto error;
|
|
}
|
|
|
|
return ctx;
|
|
|
|
error:
|
|
apfl_ctx_destroy(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
apfl_ctx_destroy(apfl_ctx ctx)
|
|
{
|
|
if (ctx == NULL) {
|
|
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_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;
|
|
}
|
|
|
|
struct iterative_runner_tmproot_data {
|
|
struct gc *gc;
|
|
bool ok;
|
|
};
|
|
|
|
static void
|
|
ctx_register_iterative_runner_tmproot(void *opaque, struct gc_object *object)
|
|
{
|
|
struct iterative_runner_tmproot_data *data = opaque;
|
|
if (!data->ok) {
|
|
return;
|
|
}
|
|
data->ok = apfl_gc_tmproot_add(data->gc, object);
|
|
}
|
|
|
|
static bool
|
|
ctx_register_iterative_runner_inner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
{
|
|
struct iterative_runner_tmproot_data data = {
|
|
.gc = &ctx->gc,
|
|
.ok = true
|
|
};
|
|
apfl_iterative_runner_visit_gc_objects(runner, ctx_register_iterative_runner_tmproot, &data);
|
|
|
|
if (!data.ok) {
|
|
return false;
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
bool
|
|
apfl_ctx_register_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
{
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
bool out = ctx_register_iterative_runner_inner(ctx, runner);
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
return out;
|
|
}
|
|
|
|
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) { \
|
|
apfl_raise_alloc_error(ctx); \
|
|
} \
|
|
\
|
|
struct apfl_value new_value = {.type = TYPE}; \
|
|
if ((new_value.MEMB = NEW) == NULL) { \
|
|
assert(apfl_stack_drop(ctx, -1)); \
|
|
apfl_raise_alloc_error(ctx); \
|
|
} \
|
|
\
|
|
*value = new_value;
|
|
|
|
void
|
|
apfl_push_nil(apfl_ctx ctx)
|
|
{
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_NIL,
|
|
});
|
|
}
|
|
|
|
void
|
|
apfl_push_bool(apfl_ctx ctx, bool b)
|
|
{
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_BOOLEAN,
|
|
.boolean = b,
|
|
});
|
|
}
|
|
|
|
void
|
|
apfl_push_number(apfl_ctx ctx, apfl_number num)
|
|
{
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_NUMBER,
|
|
.number = num,
|
|
});
|
|
}
|
|
|
|
static struct apfl_string *
|
|
new_copied_string(struct gc *gc, struct apfl_string_view sv)
|
|
{
|
|
struct apfl_string s = apfl_string_blank();
|
|
if (!apfl_string_copy(gc->allocator, &s, sv)) {
|
|
return NULL;
|
|
}
|
|
|
|
struct apfl_string *out = apfl_string_move_into_new_gc_string(gc, &s);
|
|
if (out == NULL) {
|
|
apfl_string_deinit(gc->allocator, &s);
|
|
return NULL;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
void
|
|
apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv)
|
|
{
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
ctx,
|
|
VALUE_STRING,
|
|
string,
|
|
new_copied_string(&ctx->gc, sv)
|
|
)
|
|
}
|
|
|
|
static bool
|
|
try_push_const_string(apfl_ctx ctx, const char *string)
|
|
{
|
|
return apfl_stack_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_CONST_STRING,
|
|
.const_string = apfl_string_view_from(string),
|
|
});
|
|
}
|
|
|
|
void
|
|
apfl_push_const_string(apfl_ctx ctx, const char *string)
|
|
{
|
|
if (!try_push_const_string(ctx, string)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
}
|
|
|
|
void
|
|
apfl_list_create(apfl_ctx ctx, size_t initial_cap)
|
|
{
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
ctx,
|
|
VALUE_LIST,
|
|
list,
|
|
apfl_list_new(&ctx->gc, initial_cap)
|
|
)
|
|
}
|
|
|
|
void
|
|
apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_index)
|
|
{
|
|
struct apfl_value *list_val = stack_get_pointer(ctx, list_index);
|
|
if (list_val == NULL) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (list_val->type != VALUE_LIST) {
|
|
apfl_raise_errorfmt(
|
|
ctx,
|
|
"Can not append to value of type {value:type}, expected list",
|
|
*list_val
|
|
);
|
|
}
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, value_index);
|
|
|
|
if (!apfl_list_splice(
|
|
&ctx->gc,
|
|
&list_val->list,
|
|
list_val->list->len,
|
|
0,
|
|
&value,
|
|
1
|
|
)) {
|
|
assert(apfl_stack_drop(ctx, value_index));
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
assert(apfl_stack_drop(ctx, value_index));
|
|
}
|
|
|
|
void
|
|
apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_index)
|
|
{
|
|
struct apfl_value *dst_val = stack_get_pointer(ctx, dst_index);
|
|
if (dst_val == NULL) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (dst_val->type != VALUE_LIST) {
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
|
}
|
|
|
|
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));
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
|
}
|
|
|
|
if (!apfl_list_splice(
|
|
&ctx->gc,
|
|
&dst_val->list,
|
|
dst_val->list->len,
|
|
0,
|
|
src_val.list->items,
|
|
src_val.list->len
|
|
)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
assert(apfl_stack_drop(ctx, src_index));
|
|
}
|
|
|
|
void
|
|
apfl_dict_create(apfl_ctx ctx)
|
|
{
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
ctx,
|
|
VALUE_DICT,
|
|
dict,
|
|
apfl_dict_new(&ctx->gc)
|
|
)
|
|
}
|
|
|
|
void
|
|
apfl_dict_set(
|
|
apfl_ctx ctx,
|
|
apfl_stackidx dict_index,
|
|
apfl_stackidx k_index,
|
|
apfl_stackidx v_index
|
|
) {
|
|
struct apfl_value k;
|
|
struct apfl_value v;
|
|
struct apfl_value *dict_value;
|
|
|
|
if (
|
|
!apfl_stack_get(ctx, &k, k_index)
|
|
|| !apfl_stack_get(ctx, &v, v_index)
|
|
|| (dict_value = stack_get_pointer(ctx, dict_index)) == NULL
|
|
) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (dict_value->type != VALUE_DICT) {
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_dict);
|
|
}
|
|
|
|
if (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) {
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
}
|
|
|
|
bool
|
|
apfl_get_member_if_exists(
|
|
apfl_ctx ctx,
|
|
apfl_stackidx container_index,
|
|
apfl_stackidx k_index
|
|
) {
|
|
struct apfl_value container;
|
|
struct apfl_value k;
|
|
if (
|
|
!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);
|
|
}
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
if (value == NULL) {
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
enum get_item_result result = apfl_value_get_item(container, k, value);
|
|
if (result != GET_ITEM_OK) {
|
|
assert(apfl_stack_drop(ctx, -1)); // Drop the placeholder
|
|
}
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
|
|
|
switch (result) {
|
|
case GET_ITEM_OK:
|
|
return true;
|
|
case GET_ITEM_KEY_DOESNT_EXIST:
|
|
return false;
|
|
case GET_ITEM_NOT_A_CONTAINER:
|
|
apfl_raise_errorfmt(
|
|
ctx,
|
|
"Can not get member of value of type {value:type}, not a container",
|
|
container
|
|
);
|
|
break;
|
|
case GET_ITEM_WRONG_KEY_TYPE:
|
|
apfl_raise_errorfmt(
|
|
ctx,
|
|
"Value of type {value:type} is not a valid key for container type {value:type}",
|
|
k,
|
|
container
|
|
);
|
|
break;
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
void
|
|
apfl_get_member(
|
|
apfl_ctx ctx,
|
|
apfl_stackidx container_index,
|
|
apfl_stackidx k_index
|
|
) {
|
|
if (!apfl_get_member_if_exists(ctx, container_index, k_index)) {
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
|
}
|
|
}
|
|
|
|
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_messages.not_a_list);
|
|
}
|
|
|
|
if (index >= list.list->len) {
|
|
apfl_raise_const_error(ctx, 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]);
|
|
}
|
|
|
|
void
|
|
apfl_set_list_member_by_index(
|
|
apfl_ctx ctx,
|
|
apfl_stackidx list_index,
|
|
size_t index_in_list,
|
|
apfl_stackidx value_index
|
|
) {
|
|
struct apfl_value *list = stack_get_pointer(ctx, list_index);
|
|
if (list == NULL) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (list->type != VALUE_LIST) {
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
|
}
|
|
|
|
if (index_in_list >= list->list->len) {
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
|
}
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, value_index);
|
|
|
|
if (!apfl_list_splice(
|
|
&ctx->gc,
|
|
&list->list,
|
|
index_in_list,
|
|
1,
|
|
&value,
|
|
1
|
|
)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
apfl_drop(ctx, value_index);
|
|
}
|
|
|
|
size_t
|
|
apfl_len(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
switch (value.type) {
|
|
case VALUE_NIL:
|
|
case VALUE_BOOLEAN:
|
|
case VALUE_NUMBER:
|
|
case VALUE_FUNC:
|
|
case VALUE_CFUNC:
|
|
case VALUE_USERDATA:
|
|
case VALUE_NATIVE_OBJECT:
|
|
apfl_raise_errorfmt(
|
|
ctx,
|
|
"Can not get length of value of type {value:type}",
|
|
value
|
|
);
|
|
return 0;
|
|
case VALUE_STRING:
|
|
return value.string->len;
|
|
case VALUE_CONST_STRING:
|
|
return value.const_string.len;
|
|
case VALUE_LIST:
|
|
return apfl_list_len(value.list);
|
|
case VALUE_DICT:
|
|
return apfl_dict_len(value.dict);
|
|
}
|
|
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
get_string_view_of_value(struct apfl_string_view *sv, struct apfl_value value)
|
|
{
|
|
switch (value.type) {
|
|
case VALUE_NIL:
|
|
case VALUE_BOOLEAN:
|
|
case VALUE_NUMBER:
|
|
case VALUE_FUNC:
|
|
case VALUE_CFUNC:
|
|
case VALUE_USERDATA:
|
|
case VALUE_LIST:
|
|
case VALUE_DICT:
|
|
case VALUE_NATIVE_OBJECT:
|
|
return false;
|
|
case VALUE_STRING:
|
|
*sv = apfl_string_view_from(*value.string);
|
|
return true;
|
|
case VALUE_CONST_STRING:
|
|
*sv = value.const_string;
|
|
return true;
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
struct apfl_string_view
|
|
apfl_get_string(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_string_view sv;
|
|
if (!get_string_view_of_value(&sv, apfl_stack_must_get(ctx, index))) {
|
|
apfl_raise_errorfmt(ctx, "Expected string, got {stack:type}", index);
|
|
}
|
|
return sv;
|
|
}
|
|
|
|
enum apfl_value_type
|
|
apfl_get_type(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
return apfl_value_type_to_abstract_type(value.type);
|
|
}
|
|
|
|
void
|
|
apfl_tostring(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
apfl_move_to_top_of_stack(ctx, index);
|
|
struct apfl_value value = apfl_stack_must_get(ctx, -1);
|
|
if (apfl_value_type_to_abstract_type(value.type) == APFL_VALUE_STRING) {
|
|
return;
|
|
}
|
|
|
|
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
|
|
if (!apfl_value_format(value, apfl_io_string_writer(&sb))) {
|
|
apfl_string_builder_deinit(&sb);
|
|
apfl_stack_drop(ctx, -1);
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
struct apfl_string str = apfl_string_builder_move_string(&sb);
|
|
apfl_string_builder_deinit(&sb);
|
|
if (!apfl_move_string_onto_stack(ctx, str)) {
|
|
apfl_string_deinit(ctx->gc.allocator, &str);
|
|
apfl_stack_drop(ctx, -1);
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
apfl_stack_drop(ctx, -2); // Drop original value
|
|
}
|
|
|
|
struct apfl_string *
|
|
apfl_to_dynamic_string(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
apfl_tostring(ctx, index);
|
|
struct apfl_value value = apfl_stack_must_get(ctx, -1);
|
|
if (value.type == VALUE_STRING) {
|
|
return value.string;
|
|
}
|
|
|
|
assert(value.type == VALUE_CONST_STRING /* apfl_tostring results in either VALUE_STRING or VALUE_CONST_STRING */);
|
|
|
|
apfl_push_string_view_copy(ctx, value.const_string);
|
|
value = apfl_stack_must_get(ctx, -1);
|
|
apfl_drop(ctx, -2);
|
|
|
|
return value.string;
|
|
}
|
|
|
|
void
|
|
apfl_join_strings(apfl_ctx ctx, apfl_stackidx glue, apfl_stackidx parts)
|
|
{
|
|
apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]){parts, glue});
|
|
parts = -2;
|
|
glue = -1;
|
|
|
|
if (apfl_get_type(ctx, parts) != APFL_VALUE_LIST) {
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
|
}
|
|
size_t parts_len = apfl_len(ctx, parts);
|
|
|
|
apfl_tostring(ctx, glue);
|
|
struct apfl_string_view glue_sv = apfl_get_string(ctx, glue);
|
|
|
|
size_t total_length = 0;
|
|
|
|
for (size_t i = 0; i < parts_len; i++) {
|
|
// Convert values to strings, if necessary
|
|
apfl_get_list_member_by_index(ctx, parts, i);
|
|
if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) {
|
|
total_length += apfl_get_string(ctx, -1).len;
|
|
apfl_drop(ctx, -1);
|
|
} else {
|
|
apfl_tostring(ctx, -1);
|
|
total_length += apfl_get_string(ctx, -1).len;
|
|
apfl_set_list_member_by_index(ctx, -3, i, -1);
|
|
}
|
|
|
|
if (i > 0) {
|
|
total_length += glue_sv.len;
|
|
}
|
|
}
|
|
|
|
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
|
|
if (!apfl_string_builder_prealloc(&sb, total_length)) {
|
|
goto error;
|
|
}
|
|
|
|
for (size_t i = 0; i < parts_len; i++) {
|
|
if (i > 0) {
|
|
if (!apfl_string_builder_append(&sb, glue_sv)) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
apfl_get_list_member_by_index(ctx, parts, i);
|
|
|
|
if (!apfl_string_builder_append(&sb, apfl_get_string(ctx, -1))) {
|
|
goto error;
|
|
}
|
|
|
|
apfl_drop(ctx, -1);
|
|
}
|
|
|
|
|
|
struct apfl_string str = apfl_string_builder_move_string(&sb);
|
|
apfl_string_builder_deinit(&sb);
|
|
if (!apfl_move_string_onto_stack(ctx, str)) {
|
|
apfl_string_deinit(ctx->gc.allocator, &str);
|
|
goto error;
|
|
}
|
|
|
|
// Drop the glue and list from the stack
|
|
apfl_drop(ctx, -2);
|
|
apfl_drop(ctx, -2);
|
|
return;
|
|
|
|
error:
|
|
// Drop the glue and list from the stack
|
|
apfl_drop(ctx, -1);
|
|
apfl_drop(ctx, -1);
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
void
|
|
apfl_concat_strings(apfl_ctx ctx, apfl_stackidx a, apfl_stackidx b)
|
|
{
|
|
apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]){a, b});
|
|
apfl_tostring(ctx, -2);
|
|
apfl_tostring(ctx, -2);
|
|
|
|
struct apfl_string_view sv_a = apfl_get_string(ctx, -2);
|
|
struct apfl_string_view sv_b = apfl_get_string(ctx, -1);
|
|
|
|
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
|
|
if (!apfl_string_builder_prealloc(&sb, sv_a.len + sv_b.len)) {
|
|
goto error;
|
|
}
|
|
|
|
if (
|
|
!apfl_string_builder_append(&sb, sv_a)
|
|
|| !apfl_string_builder_append(&sb, sv_b)
|
|
) {
|
|
goto error;
|
|
}
|
|
|
|
struct apfl_string str = apfl_string_builder_move_string(&sb);
|
|
apfl_string_builder_deinit(&sb);
|
|
if (!apfl_move_string_onto_stack(ctx, str)) {
|
|
apfl_string_deinit(ctx->gc.allocator, &str);
|
|
goto error;
|
|
}
|
|
|
|
apfl_drop(ctx, -2);
|
|
apfl_drop(ctx, -2);
|
|
return;
|
|
|
|
error:
|
|
apfl_drop(ctx, -1);
|
|
apfl_drop(ctx, -1);
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
bool
|
|
apfl_is_truthy(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value = apfl_stack_must_pop(ctx, index);
|
|
switch (value.type) {
|
|
case VALUE_NIL:
|
|
return false;
|
|
case VALUE_BOOLEAN:
|
|
return value.boolean;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
apfl_number
|
|
apfl_get_number(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value = apfl_stack_must_pop(ctx, index);
|
|
if (value.type == VALUE_NUMBER) {
|
|
return value.number;
|
|
}
|
|
apfl_raise_errorfmt(ctx, "Expected number, got {value:type}", value);
|
|
}
|
|
|
|
bool
|
|
apfl_eq(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b)
|
|
{
|
|
struct apfl_value a = apfl_stack_must_get(ctx, _a);
|
|
struct apfl_value b = apfl_stack_must_get(ctx, _b);
|
|
|
|
bool eq = apfl_value_eq(a, b);
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b}));
|
|
return eq;
|
|
}
|
|
|
|
int
|
|
apfl_cmp(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b)
|
|
{
|
|
struct apfl_value a = apfl_stack_must_get(ctx, _a);
|
|
struct apfl_value b = apfl_stack_must_get(ctx, _b);
|
|
|
|
enum comparison_result result = apfl_value_cmp(a, b);
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b}));
|
|
|
|
switch (result) {
|
|
case CMP_LT:
|
|
return -1;
|
|
case CMP_EQ:
|
|
return 0;
|
|
case CMP_GT:
|
|
return 1;
|
|
case CMP_UNCOMPARABLE:
|
|
apfl_raise_errorfmt(ctx, "Values of type {value:type} can not be compared", a);
|
|
case CMP_INCOMPATIBLE_TYPES:
|
|
apfl_raise_errorfmt(ctx, "Can not compare values of incompatible types {value:type} and {value:type}", a, b);
|
|
}
|
|
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
static struct scope *
|
|
closure_scope_for_func_inner(apfl_ctx ctx, struct scopes scopes)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// The order is important here: by merging the local scope last, we make
|
|
// sure a variable from the current scope shadows a variable from the
|
|
// closure scope
|
|
|
|
if (scopes.closure != NULL) {
|
|
if (!apfl_scope_merge_into(out, scopes.closure)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (scopes.local != NULL) {
|
|
if (!apfl_scope_merge_into(out, scopes.local)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
struct scope *
|
|
apfl_closure_scope_for_func(apfl_ctx ctx, struct scopes scopes)
|
|
{
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
struct scope *out = closure_scope_for_func_inner(ctx, scopes);
|
|
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 noreturn void
|
|
raise_no_cfunc(apfl_ctx ctx)
|
|
{
|
|
apfl_raise_const_error(ctx, 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 != APFL_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_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_messages.wrong_type);
|
|
}
|
|
return value.userdata;
|
|
}
|
|
|
|
void *
|
|
apfl_push_native_object(apfl_ctx ctx, const struct apfl_native_object_type *type)
|
|
{
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
ctx,
|
|
VALUE_NATIVE_OBJECT,
|
|
native_object,
|
|
apfl_native_object_new(&ctx->gc, type)
|
|
)
|
|
|
|
return apfl_get_native_object(ctx, type, -1);
|
|
}
|
|
|
|
void *
|
|
apfl_get_native_object(apfl_ctx ctx, const struct apfl_native_object_type *type, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
if (value.type != VALUE_NATIVE_OBJECT || value.native_object->type != type) {
|
|
apfl_raise_const_error(ctx, apfl_messages.wrong_type);
|
|
}
|
|
return value.native_object->memory;
|
|
}
|
|
|
|
struct apfl_io_writer apfl_get_output_writer(apfl_ctx ctx)
|
|
{
|
|
return ctx->output_writer;
|
|
}
|
|
|
|
static bool
|
|
init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len)
|
|
{
|
|
if (len == 0) {
|
|
return true;
|
|
}
|
|
|
|
if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
(*list)[i] = (struct apfl_value) { .type = VALUE_NIL };
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct matcher *
|
|
apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist)
|
|
{
|
|
struct matcher matcher = {
|
|
.instructions = milist,
|
|
.value_count = 0,
|
|
.values = NULL,
|
|
};
|
|
|
|
if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) {
|
|
goto error;
|
|
}
|
|
matcher.value_count = milist->value_count;
|
|
|
|
struct matcher *gc_matcher = apfl_gc_new_matcher(gc);
|
|
if (gc_matcher == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
*gc_matcher = matcher;
|
|
|
|
return gc_matcher;
|
|
|
|
error:
|
|
apfl_matcher_deinit(gc->allocator, &matcher);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher)
|
|
{
|
|
FREE_LIST(allocator, matcher->values, matcher->value_count);
|
|
}
|
|
|
|
void
|
|
apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque)
|
|
{
|
|
visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS));
|
|
|
|
for (size_t i = 0; i < matcher->instructions->value_count; i++) {
|
|
apfl_value_visit_gc_object(matcher->values[i], visitor, opaque);
|
|
}
|
|
}
|
|
|
|
size_t
|
|
apfl_call_stack_depth(apfl_ctx ctx)
|
|
{
|
|
return ctx->call_stack.len;
|
|
}
|
|
|
|
static struct apfl_string_view
|
|
get_string_view_or_empty(struct apfl_string *s)
|
|
{
|
|
return s == NULL
|
|
? (struct apfl_string_view) { .len = 0, .bytes = NULL, }
|
|
: apfl_string_view_from(*s);
|
|
}
|
|
|
|
struct apfl_call_stack_entry_info
|
|
apfl_call_stack_inspect(apfl_ctx ctx, size_t n)
|
|
{
|
|
if (n >= ctx->call_stack.len) {
|
|
apfl_raise_const_error(ctx, apfl_messages.invalid_call_stack_index);
|
|
}
|
|
|
|
struct call_stack_entry *cse = &ctx->call_stack.items[ctx->call_stack.len - n - 1];
|
|
|
|
struct apfl_call_stack_entry_info info = {
|
|
.type = cse->type,
|
|
.name = (struct apfl_string_view) { .len = 0, .bytes = NULL, },
|
|
.filename = (struct apfl_string_view) { .len = 0, .bytes = NULL, },
|
|
.toplevel = false,
|
|
};
|
|
|
|
switch (cse->type) {
|
|
case APFL_CSE_FUNCTION:
|
|
info.line_current = cse->func.execution_line;
|
|
info.filename = get_string_view_or_empty(cse->func.instructions->filename);
|
|
if (cse->func.function == NULL) {
|
|
info.toplevel = true;
|
|
} else {
|
|
info.line_defined = cse->func.instructions->line;
|
|
info.subfunction_index = cse->func.subfunction_index;
|
|
|
|
struct apfl_string_view sv;
|
|
if (apfl_func_get_name(cse->func.function, &sv)) {
|
|
info.name = sv;
|
|
}
|
|
}
|
|
break;
|
|
case APFL_CSE_CFUNCTION:
|
|
info.name = get_string_view_or_empty(cse->cfunc.func->name);
|
|
break;
|
|
case APFL_CSE_FUNCTION_DISPATCH: {
|
|
struct apfl_string_view sv;
|
|
if (apfl_func_get_name(cse->func_dispatch.function, &sv)) {
|
|
info.name = sv;
|
|
}
|
|
info.line_defined = cse->func_dispatch.function->line_defined;
|
|
info.filename = get_string_view_or_empty(cse->func_dispatch.function->filename);
|
|
break;
|
|
}
|
|
case APFL_CSE_MATCHER:
|
|
break;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
static bool
|
|
format_defined_at(
|
|
struct apfl_io_writer w,
|
|
struct apfl_call_stack_entry_info info,
|
|
struct apfl_string_view *filename
|
|
) {
|
|
FMT_TRY(apfl_io_write_string(w, "defined in "));
|
|
if (filename != NULL && filename->len > 0) {
|
|
FMT_TRY(apfl_io_write_string(w, "file "));
|
|
FMT_TRY(apfl_io_write_string(w, *filename));
|
|
FMT_TRY(apfl_io_write_string(w, ", "));
|
|
}
|
|
FMT_TRY(apfl_io_write_string(w, "line "));
|
|
FMT_TRY(apfl_format_put_int(w, info.line_defined));
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
apfl_call_stack_entry_info_format(struct apfl_io_writer w, struct apfl_call_stack_entry_info info)
|
|
{
|
|
switch (info.type) {
|
|
case APFL_CSE_FUNCTION:
|
|
if (info.filename.len > 0) {
|
|
FMT_TRY(apfl_io_write_string(w, "File "));
|
|
FMT_TRY(apfl_io_write_string(w, info.filename));
|
|
FMT_TRY(apfl_io_write_string(w, ", "));
|
|
}
|
|
FMT_TRY(apfl_io_write_string(w, "Line "));
|
|
FMT_TRY(apfl_format_put_int(w, info.line_current));
|
|
FMT_TRY(apfl_io_write_string(w, ", "));
|
|
|
|
if (info.toplevel) {
|
|
FMT_TRY(apfl_io_write_string(w, "toplevel "));
|
|
} else if (info.name.len == 0) {
|
|
FMT_TRY(apfl_io_write_string(w, "anonymous function (subfunction "));
|
|
FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index));
|
|
FMT_TRY(apfl_io_write_string(w, "; "));
|
|
FMT_TRY(format_defined_at(w, info, NULL));
|
|
FMT_TRY(apfl_io_write_string(w, ")"));
|
|
} else {
|
|
FMT_TRY(apfl_io_write_string(w, "function "));
|
|
FMT_TRY(apfl_io_write_string(w, info.name));
|
|
FMT_TRY(apfl_io_write_string(w, " (subfunction "));
|
|
FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index));
|
|
FMT_TRY(apfl_io_write_string(w, "; "));
|
|
FMT_TRY(format_defined_at(w, info, NULL));
|
|
FMT_TRY(apfl_io_write_string(w, ")"));
|
|
}
|
|
break;
|
|
case APFL_CSE_CFUNCTION:
|
|
if (info.name.len == 0) {
|
|
FMT_TRY(apfl_io_write_string(w, "Anonymous native function"));
|
|
} else {
|
|
FMT_TRY(apfl_io_write_string(w, "Native function "));
|
|
FMT_TRY(apfl_io_write_string(w, info.name));
|
|
}
|
|
break;
|
|
case APFL_CSE_FUNCTION_DISPATCH:
|
|
FMT_TRY(apfl_io_write_string(w, "Dispatch for "));
|
|
if (info.name.len == 0) {
|
|
FMT_TRY(apfl_io_write_string(w, "anonymous function ("));
|
|
} else {
|
|
FMT_TRY(apfl_io_write_string(w, "function "));
|
|
FMT_TRY(apfl_io_write_string(w, info.name));
|
|
FMT_TRY(apfl_io_write_string(w, "("));
|
|
}
|
|
FMT_TRY(format_defined_at(w, info, &info.filename));
|
|
FMT_TRY(apfl_io_write_string(w, ")"));
|
|
break;
|
|
case APFL_CSE_MATCHER:
|
|
FMT_TRY(apfl_io_write_string(w, "Matcher"));
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct instruction_list *
|
|
setup_function_for_load_inner(apfl_ctx ctx, struct apfl_string *filename)
|
|
{
|
|
struct apfl_value *func_value = apfl_stack_push_placeholder(ctx);
|
|
if (func_value == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if ((func_value->func = apfl_func_new(&ctx->gc, 1, NULL, 1, filename)) == NULL) {
|
|
apfl_drop(ctx, -1);
|
|
return NULL;
|
|
}
|
|
func_value->type = VALUE_FUNC;
|
|
|
|
struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, 1, filename);
|
|
if (
|
|
ilist == NULL
|
|
|| !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))
|
|
) {
|
|
return NULL;
|
|
}
|
|
|
|
struct matcher_instruction_list *milist = apfl_matcher_instructions_new(&ctx->gc);
|
|
if (
|
|
milist == NULL
|
|
|| !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(milist, GC_TYPE_MATCHER_INSTRUCTIONS))
|
|
) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!apfl_resizable_append(
|
|
ctx->gc.allocator,
|
|
sizeof(union matcher_instruction_or_arg),
|
|
(void **)&milist->instructions,
|
|
&milist->len,
|
|
&milist->cap,
|
|
&(union matcher_instruction_or_arg[]) {
|
|
{.instruction = MATCHER_IGNORE},
|
|
},
|
|
1
|
|
)) {
|
|
return NULL;
|
|
}
|
|
|
|
struct matcher *matcher = apfl_matcher_new(&ctx->gc, milist);
|
|
if (
|
|
matcher == NULL
|
|
|| !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(matcher, GC_TYPE_MATCHER))
|
|
) {
|
|
apfl_drop(ctx, -1);
|
|
return NULL;
|
|
}
|
|
|
|
if (!apfl_func_add_subfunc(func_value->func, ilist, matcher)) {
|
|
apfl_drop(ctx, -1);
|
|
return NULL;
|
|
}
|
|
|
|
return ilist;
|
|
}
|
|
|
|
static struct instruction_list *
|
|
setup_function_for_load(apfl_ctx ctx, struct apfl_string *filename)
|
|
{
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
struct instruction_list *out = setup_function_for_load_inner(ctx, filename);
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
return out;
|
|
}
|
|
|
|
void
|
|
apfl_load(apfl_ctx ctx, struct apfl_source_reader reader, apfl_stackidx name)
|
|
{
|
|
struct apfl_string *filename = apfl_to_dynamic_string(ctx, name);
|
|
|
|
apfl_tokenizer_ptr tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader);
|
|
if (tokenizer == NULL) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
apfl_parser_ptr parser = apfl_parser_new(
|
|
ctx->gc.allocator,
|
|
apfl_tokenizer_as_token_source(tokenizer)
|
|
);
|
|
if (parser == NULL) {
|
|
apfl_tokenizer_destroy(tokenizer);
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
struct instruction_list *ilist = setup_function_for_load(ctx, filename);
|
|
if (ilist == NULL) {
|
|
apfl_parser_destroy(parser);
|
|
apfl_tokenizer_destroy(tokenizer);
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
for (;;) {
|
|
switch (apfl_parser_next(parser)) {
|
|
case APFL_PARSE_OK: {
|
|
struct apfl_error err;
|
|
if (!apfl_compile(
|
|
&ctx->gc,
|
|
apfl_parser_get_expr(parser),
|
|
&err,
|
|
ilist
|
|
)) {
|
|
apfl_drop(ctx, -1);
|
|
apfl_parser_destroy(parser);
|
|
apfl_tokenizer_destroy(tokenizer);
|
|
apfl_raise_error_object(ctx, err);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case APFL_PARSE_ERROR:
|
|
apfl_drop(ctx, -1);
|
|
apfl_parser_destroy(parser);
|
|
apfl_tokenizer_destroy(tokenizer);
|
|
apfl_raise_error_object(ctx, apfl_parser_get_error(parser));
|
|
break;
|
|
case APFL_PARSE_EOF:
|
|
apfl_parser_destroy(parser);
|
|
apfl_tokenizer_destroy(tokenizer);
|
|
return;
|
|
}
|
|
}
|
|
}
|