Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include <assert.h>
|
2023-01-24 20:22:22 +00:00
|
|
|
#include <stdarg.h>
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2022-06-24 21:13:44 +00:00
|
|
|
#include <setjmp.h>
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
#include "apfl.h"
|
|
|
|
|
#include "alloc.h"
|
2023-01-30 21:50:01 +00:00
|
|
|
#include "compile.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include "context.h"
|
|
|
|
|
#include "gc.h"
|
2023-11-23 19:29:38 +00:00
|
|
|
#include "globals.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include "hashmap.h"
|
2023-03-05 21:55:20 +00:00
|
|
|
#include "modules.h"
|
2023-03-30 20:11:44 +00:00
|
|
|
#include "registry.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include "resizable.h"
|
2022-04-22 21:17:28 +00:00
|
|
|
#include "strings.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include "value.h"
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
static bool try_push_const_string(apfl_ctx ctx, const char *string);
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn static void
|
2023-01-28 20:41:48 +00:00
|
|
|
panic(apfl_ctx ctx, enum apfl_result result)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
|
|
|
|
(void)ctx;
|
|
|
|
|
(void)result;
|
|
|
|
|
fprintf(stderr, "panic!\n");
|
|
|
|
|
// TODO: more details
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-28 20:41:48 +00:00
|
|
|
struct protected_errcallback_data {
|
|
|
|
|
void *opaque_outer;
|
|
|
|
|
void (*errcallback)(apfl_ctx, void *);
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-03 21:39:29 +00:00
|
|
|
static void
|
2023-01-28 20:41:48 +00:00
|
|
|
protected_errcallback(apfl_ctx ctx, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
struct protected_errcallback_data *data = opaque;
|
|
|
|
|
data->errcallback(ctx, data->opaque_outer);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-03 21:39:29 +00:00
|
|
|
static void
|
|
|
|
|
protected_run_deferreds(apfl_ctx ctx, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
struct call_stack_entry *cse = opaque;
|
|
|
|
|
apfl_cfunc_run_deferred(ctx, cse);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
protected_in_error_handling(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
void (*callback)(apfl_ctx, void *),
|
|
|
|
|
void *opaque,
|
|
|
|
|
enum apfl_result *result,
|
|
|
|
|
bool *with_error_on_stack
|
|
|
|
|
) {
|
|
|
|
|
switch (apfl_do_protected(ctx, callback, opaque, 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
enum apfl_result
|
2022-11-20 20:42:46 +00:00
|
|
|
apfl_do_protected(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
void (*callback)(apfl_ctx, void *),
|
|
|
|
|
void *opaque,
|
2023-01-28 20:41:48 +00:00
|
|
|
void (*errcallback)(apfl_ctx, void *)
|
2022-11-20 20:42:46 +00:00
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
struct error_handler *prev_handler = ctx->error_handler;
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
size_t callstack_len = ctx->call_stack.len;
|
2022-06-24 21:13:44 +00:00
|
|
|
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) {
|
2022-11-20 20:42:46 +00:00
|
|
|
callback(ctx, opaque);
|
2022-06-24 21:13:44 +00:00
|
|
|
} else {
|
|
|
|
|
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
|
|
|
|
|
assert(result != APFL_RESULT_OK);
|
|
|
|
|
|
2023-01-28 20:41:48 +00:00
|
|
|
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,
|
|
|
|
|
};
|
2023-07-03 21:39:29 +00:00
|
|
|
protected_in_error_handling(ctx, protected_errcallback, &data, &result, &with_error_on_stack);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-28 20:41:48 +00:00
|
|
|
struct apfl_value err;
|
|
|
|
|
if (with_error_on_stack && !apfl_stack_pop(ctx, &err, -1)) {
|
|
|
|
|
result = APFL_RESULT_ERRERR;
|
|
|
|
|
with_error_on_stack = false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
2023-01-28 20:41:48 +00:00
|
|
|
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;
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2023-01-28 20:41:48 +00:00
|
|
|
assert(callstack_len <= ctx->call_stack.cap);
|
2023-07-03 21:39:29 +00:00
|
|
|
for (size_t i = ctx->call_stack.len; i-- > callstack_len; ) {
|
|
|
|
|
struct call_stack_entry *cse = &ctx->call_stack.items[i];
|
|
|
|
|
if (cse->type == APFL_CSE_CFUNCTION) {
|
|
|
|
|
protected_in_error_handling(ctx, protected_run_deferreds, cse, &result, &with_error_on_stack);
|
|
|
|
|
}
|
|
|
|
|
apfl_call_stack_entry_deinit(ctx->gc.allocator, cse);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = 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
|
2022-07-11 19:41:05 +00:00
|
|
|
);
|
2023-07-02 14:12:51 +00:00
|
|
|
assert(ok /* Shrinking should not fail */);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
2023-01-28 20:41:48 +00:00
|
|
|
if (with_error_on_stack && !apfl_stack_push(ctx, err)) {
|
|
|
|
|
result = APFL_RESULT_ERRERR;
|
|
|
|
|
with_error_on_stack = false;
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
2023-01-28 20:41:48 +00:00
|
|
|
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx->error_handler = prev_handler;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-20 20:42:46 +00:00
|
|
|
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
|
2023-01-28 20:41:48 +00:00
|
|
|
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args)
|
2022-11-20 20:42:46 +00:00
|
|
|
{
|
|
|
|
|
struct call_protected_data data = {
|
|
|
|
|
.func = func,
|
|
|
|
|
.args = args,
|
|
|
|
|
};
|
2023-01-28 20:41:48 +00:00
|
|
|
return apfl_do_protected(ctx, call_protected_cb, &data, NULL);
|
2022-11-20 20:42:46 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn static void
|
2023-01-28 20:41:48 +00:00
|
|
|
raise_error(apfl_ctx ctx, enum apfl_result type)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
|
|
|
|
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) {
|
2023-01-28 20:41:48 +00:00
|
|
|
ctx->panic_callback(ctx, ctx->panic_callback_data, type);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-28 20:41:48 +00:00
|
|
|
panic(ctx, type);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn void
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_error(apfl_ctx ctx, apfl_stackidx idx)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
2023-01-28 20:41:48 +00:00
|
|
|
if (!current_stack_move_to_top(ctx, idx)) {
|
|
|
|
|
raise_error(ctx, APFL_RESULT_ERRERR);
|
|
|
|
|
}
|
|
|
|
|
raise_error(ctx, APFL_RESULT_ERR);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn void
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(apfl_ctx ctx, const char *message)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
2023-01-28 20:41:48 +00:00
|
|
|
if (!try_push_const_string(ctx, message)) {
|
|
|
|
|
raise_error(ctx, APFL_RESULT_ERRERR);
|
|
|
|
|
}
|
|
|
|
|
raise_error(ctx, APFL_RESULT_ERR);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn void
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(apfl_ctx ctx)
|
|
|
|
|
{
|
2023-01-28 20:41:48 +00:00
|
|
|
raise_error(ctx, APFL_RESULT_ERR_ALLOC);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn void
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.invalid_stack_index);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn void
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_error_object(apfl_ctx ctx, struct apfl_error error)
|
|
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
if (error.type == APFL_ERR_MALLOC_FAILED) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *const_str = apfl_error_as_const_string(error);
|
|
|
|
|
if (const_str != NULL) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, const_str);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_error(ctx, -1);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-24 20:22:22 +00:00
|
|
|
struct errorfmt_data {
|
|
|
|
|
va_list ap;
|
|
|
|
|
apfl_ctx ctx;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool
|
2023-02-10 20:38:54 +00:00
|
|
|
errorfmt_str(void *opaque, struct apfl_io_writer w, struct apfl_string_view arg)
|
2023-01-24 20:22:22 +00:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 20:38:54 +00:00
|
|
|
return apfl_io_write_string_view(w, sv);
|
2023-01-24 20:22:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
2023-02-10 20:38:54 +00:00
|
|
|
errorfmt_type(void *opaque, struct apfl_io_writer w, struct apfl_string_view arg)
|
2023-01-24 20:22:22 +00:00
|
|
|
{
|
|
|
|
|
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)) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "{stack:type => invalid stack index}"));
|
2023-01-24 20:22:22 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 20:38:54 +00:00
|
|
|
return apfl_io_write_string_view(w, apfl_string_view_from(apfl_type_name(type)));
|
2023-01-24 20:22:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct format_spec errorfmt_specs[] = {
|
|
|
|
|
{"string", errorfmt_str},
|
|
|
|
|
{"type", errorfmt_type},
|
|
|
|
|
{NULL, NULL},
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn void
|
2023-01-24 20:22:22 +00:00
|
|
|
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);
|
2023-02-10 20:38:54 +00:00
|
|
|
struct apfl_io_writer w = apfl_io_string_writer(&sb);
|
2023-01-24 20:22:22 +00:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
|
|
|
|
apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
ctx->panic_callback = cb;
|
|
|
|
|
ctx->panic_callback_data = opaque;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack
|
|
|
|
|
apfl_stack_new(void)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
2022-07-01 20:40:43 +00:00
|
|
|
return (struct stack) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
.items = NULL,
|
|
|
|
|
.len = 0,
|
|
|
|
|
.cap = 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
|
|
|
|
apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value)
|
|
|
|
|
{
|
|
|
|
|
if (!apfl_stack_push(ctx, value)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_push(apfl_ctx ctx, struct apfl_value value)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return apfl_resizable_append(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
|
|
|
|
&stack->cap,
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
&value,
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
stack_check_index(struct stack *stack, apfl_stackidx *index)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
|
|
|
|
if (*index < 0) {
|
2022-07-11 19:41:05 +00:00
|
|
|
if ((size_t)-*index > stack->len) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
*index = stack->len + *index;
|
|
|
|
|
} else if ((size_t)*index >= stack->len) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(0 <= *index && (size_t)*index < stack->len);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static int
|
|
|
|
|
cmp_stackidx(const void *_a, const void *_b)
|
|
|
|
|
{
|
|
|
|
|
const apfl_stackidx *a = _a;
|
|
|
|
|
const apfl_stackidx *b = _b;
|
|
|
|
|
return *a - *b;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
for (size_t i = 0; i < count; i++) {
|
2022-07-11 19:41:05 +00:00
|
|
|
if (!stack_check_index(stack, &indices[i])) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qsort(indices, count, sizeof(apfl_stackidx), cmp_stackidx);
|
|
|
|
|
|
|
|
|
|
for (size_t i = count; i-- > 0; ) {
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = apfl_resizable_cut_without_resize(
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
indices[i],
|
|
|
|
|
1
|
2023-07-02 14:12:51 +00:00
|
|
|
);
|
|
|
|
|
assert(ok /* Will not fail, as we've already checked the indices */);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Shrink stack
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
|
|
|
|
if (!stack_check_index(stack, &index)) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
*value = stack->items[index];
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = apfl_resizable_splice(
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
|
|
|
|
&stack->cap,
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
index,
|
|
|
|
|
1,
|
|
|
|
|
NULL,
|
|
|
|
|
0
|
2023-07-02 14:12:51 +00:00
|
|
|
);
|
|
|
|
|
assert(ok /* Shrinking shouldn't fail */);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static struct apfl_value *
|
|
|
|
|
stack_get_pointer(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
|
|
|
|
if (!stack_check_index(stack, &index)) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
return &stack->items[index];
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
2022-07-11 19:41:05 +00:00
|
|
|
stack_get_and_adjust_index(struct stack *stack, struct apfl_value *value, apfl_stackidx *index)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
if (!stack_check_index(stack, index)) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
*value = stack->items[*index];
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
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;
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-21 20:40:27 +00:00
|
|
|
struct apfl_value *
|
|
|
|
|
apfl_stack_push_placeholder(apfl_ctx ctx)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
|
|
|
|
if (!apfl_stack_push(ctx, (struct apfl_value) {.type = VALUE_NIL})) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stack_get_pointer(ctx, -1);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value;
|
|
|
|
|
return apfl_stack_pop(ctx, &value, index);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
void
|
|
|
|
|
apfl_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
if (!apfl_stack_drop(ctx, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
bool
|
|
|
|
|
current_stack_multi_move_to_top(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
|
if (!stack_check_index(stack, &indices[i])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
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];
|
2022-06-24 21:13:44 +00:00
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
memmove(
|
|
|
|
|
&stack->items[absindex],
|
|
|
|
|
&stack->items[absindex + 1],
|
|
|
|
|
sizeof(struct apfl_value) * (stack->len - absindex - 1)
|
|
|
|
|
);
|
|
|
|
|
stack->items[stack->len-1] = val;
|
2022-06-24 21:13:44 +00:00
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
for (size_t j = i + 1; j < count; j++) {
|
|
|
|
|
if (indices[j] >= indices[i]) {
|
|
|
|
|
indices[j]--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
static bool
|
|
|
|
|
current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
return current_stack_multi_move_to_top(ctx, 1, &index);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 21:44:34 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-31 14:51:56 +00:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 19:15:20 +00:00
|
|
|
void
|
|
|
|
|
apfl_stack_clear(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
stack->len = 0;
|
2022-07-01 20:40:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2022-07-11 19:41:05 +00:00
|
|
|
stack_traverse(struct stack stack, gc_visitor visitor, void *opaque)
|
2022-07-01 20:40:43 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
for (size_t i = 0; i < stack.len; i++) {
|
2022-11-19 21:06:23 +00:00
|
|
|
apfl_value_visit_gc_object(stack.items[i], visitor, opaque);
|
2022-07-01 20:40:43 +00:00
|
|
|
}
|
2022-04-21 19:15:20 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_scopes(struct scopes scopes, gc_visitor visitor, void *opaque)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_nullable_scope(scopes.local, visitor, opaque);
|
|
|
|
|
visit_nullable_scope(scopes.closure, visitor, opaque);
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
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++) {
|
2022-07-11 19:41:05 +00:00
|
|
|
visitor(
|
|
|
|
|
opaque,
|
2022-11-19 21:06:23 +00:00
|
|
|
GC_OBJECT_FROM(matcher_stack.items[i], GC_TYPE_MATCHER)
|
2022-07-11 19:41:05 +00:00
|
|
|
);
|
2022-11-19 21:06:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
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)
|
|
|
|
|
);
|
2022-08-04 20:23:21 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
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);
|
2022-07-11 19:41:05 +00:00
|
|
|
visitor(
|
|
|
|
|
opaque,
|
2022-11-19 21:06:23 +00:00
|
|
|
GC_OBJECT_FROM(cse.transfers[i].var, GC_TYPE_STRING)
|
2022-07-11 19:41:05 +00:00
|
|
|
);
|
2022-11-19 21:06:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_FUNCTION:
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_func_cse(cse.func, visitor, opaque);
|
|
|
|
|
break;
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_CFUNCTION:
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_cfunc_cse(cse.cfunc, visitor, opaque);
|
2022-07-11 19:41:05 +00:00
|
|
|
break;
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_MATCHER:
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_matcher_cse(cse.matcher, visitor, opaque);
|
2022-07-28 18:46:32 +00:00
|
|
|
break;
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_FUNCTION_DISPATCH:
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_func_dispatch_cse(cse.func_dispatch, visitor, opaque);
|
2022-08-12 22:50:26 +00:00
|
|
|
break;
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 20:00:58 +00:00
|
|
|
static void
|
|
|
|
|
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
|
|
|
|
|
{
|
|
|
|
|
apfl_ctx ctx = own_opaque;
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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);
|
2022-07-01 20:00:58 +00:00
|
|
|
}
|
2023-03-30 20:11:44 +00:00
|
|
|
|
|
|
|
|
apfl_registry_visit_gc_objects(ctx->registry, visitor, visitor_opaque);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-03 21:39:29 +00:00
|
|
|
static void
|
|
|
|
|
cfunc_call_stack_entry_deinit(struct apfl_allocator allocator, struct cfunc_call_stack_entry *cse)
|
|
|
|
|
{
|
|
|
|
|
FREE_LIST(allocator, cse->deferred_list, cse->deferred_cap);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
void
|
|
|
|
|
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
|
|
|
|
|
{
|
|
|
|
|
deinit_stack(allocator, &entry->stack);
|
2022-07-28 18:46:32 +00:00
|
|
|
|
|
|
|
|
switch (entry->type) {
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_FUNCTION:
|
2022-11-19 21:06:23 +00:00
|
|
|
func_call_stack_entry_deinit(allocator, &entry->func);
|
|
|
|
|
break;
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_CFUNCTION:
|
2023-07-03 21:39:29 +00:00
|
|
|
cfunc_call_stack_entry_deinit(allocator, &entry->cfunc);
|
|
|
|
|
break;
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_FUNCTION_DISPATCH:
|
2022-07-28 18:46:32 +00:00
|
|
|
break;
|
2023-01-26 20:22:34 +00:00
|
|
|
case APFL_CSE_MATCHER:
|
2022-11-19 21:06:23 +00:00
|
|
|
apfl_matcher_call_stack_entry_deinit(allocator, &entry->matcher);
|
2022-07-28 18:46:32 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
2022-07-01 20:00:58 +00:00
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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,
|
|
|
|
|
};
|
2022-07-01 20:00:58 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
static void
|
|
|
|
|
init_globals_protected(apfl_ctx ctx, void *opaque)
|
2022-07-12 20:13:07 +00:00
|
|
|
{
|
2023-03-05 21:55:20 +00:00
|
|
|
(void)opaque;
|
2022-07-12 20:13:07 +00:00
|
|
|
|
2023-11-23 19:29:38 +00:00
|
|
|
apfl_globals(ctx);
|
2022-07-12 20:13:07 +00:00
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
struct apfl_value val = apfl_stack_must_get(ctx, -1);
|
|
|
|
|
if (val.type != VALUE_DICT) {
|
|
|
|
|
apfl_raise_const_error(ctx, "Globals didn't return a dict");
|
|
|
|
|
}
|
2022-07-12 20:13:07 +00:00
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
HASHMAP_EACH(&val.dict->map, cur) {
|
|
|
|
|
struct apfl_value *k = apfl_hashmap_cursor_peek_key(cur);
|
|
|
|
|
apfl_stack_must_push(ctx, *k);
|
|
|
|
|
struct apfl_string *name = apfl_to_dynamic_string(ctx, -1);
|
2022-07-12 20:13:07 +00:00
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
struct apfl_value *v = apfl_hashmap_cursor_peek_value(cur);
|
2023-01-24 20:59:54 +00:00
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
if (!apfl_scope_set(&ctx->gc, ctx->globals, name, *v)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
2022-07-12 20:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
apfl_drop(ctx, -1);
|
2022-07-12 20:13:07 +00:00
|
|
|
}
|
2023-03-05 21:55:20 +00:00
|
|
|
}
|
2022-07-12 20:13:07 +00:00
|
|
|
|
2023-07-03 21:39:29 +00:00
|
|
|
static void
|
|
|
|
|
init_standard_modules_protected(apfl_ctx ctx, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
(void)opaque;
|
|
|
|
|
|
|
|
|
|
apfl_push_cfunc(ctx, apfl_module_re, 0);
|
|
|
|
|
apfl_modules_register(ctx, "re", -1);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-04 20:50:12 +00:00
|
|
|
#define DEBUG_INIT_GLOBALS 1
|
|
|
|
|
|
|
|
|
|
#if DEBUG_INIT_GLOBALS
|
|
|
|
|
static void
|
|
|
|
|
on_init_globals_error(apfl_ctx ctx, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
(void)opaque;
|
|
|
|
|
|
|
|
|
|
struct apfl_io_writer w = apfl_io_file_writer(stderr);
|
|
|
|
|
|
|
|
|
|
apfl_tostring(ctx, -1);
|
|
|
|
|
|
|
|
|
|
if (!(
|
|
|
|
|
apfl_io_write_string(w, apfl_get_string(ctx, -1))
|
|
|
|
|
&& apfl_io_write_string(w, "\n\nBacktrace:")
|
|
|
|
|
)) {
|
|
|
|
|
apfl_raise_const_error(ctx, "Error in on_init_globals_error");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t depth = apfl_call_stack_depth(ctx);
|
|
|
|
|
for (size_t i = 0; i < depth; i++) {
|
|
|
|
|
if (!(
|
|
|
|
|
apfl_io_write_string(w, "\n")
|
|
|
|
|
&& apfl_io_write_string(w, "#")
|
|
|
|
|
&& apfl_format_put_int(w, (int)i+1)
|
|
|
|
|
&& apfl_io_write_string(w, ": ")
|
|
|
|
|
&& apfl_call_stack_entry_info_format(
|
|
|
|
|
w,
|
|
|
|
|
apfl_call_stack_inspect(ctx, i)
|
|
|
|
|
)
|
|
|
|
|
)) {
|
|
|
|
|
apfl_raise_const_error(ctx, "Error in on_init_globals_error");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#define INIT_GLOBALS_ERRCALLBACK on_init_globals_error
|
|
|
|
|
#else
|
|
|
|
|
#define INIT_GLOBALS_ERRCALLBACK NULL
|
|
|
|
|
#endif
|
|
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
static bool
|
|
|
|
|
init_globals(apfl_ctx ctx)
|
|
|
|
|
{
|
2023-04-04 20:50:12 +00:00
|
|
|
return apfl_do_protected(ctx, init_globals_protected, NULL, INIT_GLOBALS_ERRCALLBACK) == APFL_RESULT_OK;
|
2022-07-12 20:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-03 21:39:29 +00:00
|
|
|
static bool
|
|
|
|
|
init_standard_modules(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
return apfl_do_protected(ctx, init_standard_modules_protected, NULL, INIT_GLOBALS_ERRCALLBACK) == APFL_RESULT_OK;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_ctx
|
2022-10-28 19:32:17 +00:00
|
|
|
apfl_ctx_new(struct apfl_config config)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
2022-10-28 19:32:17 +00:00
|
|
|
apfl_ctx ctx = ALLOC_OBJ(config.allocator, struct apfl_ctx_data);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
if (ctx == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 19:13:34 +00:00
|
|
|
// 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;
|
2022-07-11 19:41:05 +00:00
|
|
|
ctx->toplevel_stack = apfl_stack_new();
|
|
|
|
|
ctx->call_stack = call_stack_new();
|
2022-07-14 19:13:34 +00:00
|
|
|
ctx->globals = NULL;
|
|
|
|
|
ctx->iterative_runners = iterative_runners_list_new();
|
2023-03-30 20:11:44 +00:00
|
|
|
ctx->registry = NULL;
|
2022-07-14 19:13:34 +00:00
|
|
|
|
2022-10-28 19:32:17 +00:00
|
|
|
apfl_gc_init(&ctx->gc, config.allocator, get_roots, ctx);
|
2022-07-11 19:41:05 +00:00
|
|
|
|
|
|
|
|
if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-28 19:32:17 +00:00
|
|
|
ctx->output_writer = config.output_writer;
|
|
|
|
|
|
2023-03-30 20:11:44 +00:00
|
|
|
if ((ctx->registry = apfl_registry_new(ctx->gc.allocator)) == NULL) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
if (!init_globals(ctx)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-03 21:39:29 +00:00
|
|
|
if (!config.no_standard_modules && !init_standard_modules(ctx)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return ctx;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
apfl_ctx_destroy(ctx);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_ctx_destroy(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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();
|
|
|
|
|
|
2023-03-30 20:11:44 +00:00
|
|
|
apfl_registry_destroy(ctx->registry);
|
|
|
|
|
ctx->registry = NULL;
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
struct apfl_allocator base_allocator = ctx->gc.base_allocator;
|
|
|
|
|
|
|
|
|
|
apfl_gc_full(&ctx->gc);
|
|
|
|
|
apfl_gc_deinit(&ctx->gc);
|
|
|
|
|
|
|
|
|
|
FREE_OBJ(base_allocator, ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 19:13:34 +00:00
|
|
|
struct iterative_runner_tmproot_data {
|
|
|
|
|
struct gc *gc;
|
|
|
|
|
bool ok;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ctx_register_iterative_runner_tmproot(void *opaque, struct gc_object *object)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
2022-07-14 19:13:34 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 19:13:34 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
void
|
|
|
|
|
apfl_ctx_unregister_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
if (!find_iterative_runner(ctx, runner, &i)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = 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
|
|
|
|
|
);
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(
|
|
|
|
|
// We're only removing elements, the buffer should not grow,
|
|
|
|
|
// therefore there should be no allocation errors
|
2023-07-02 14:12:51 +00:00
|
|
|
ok
|
2022-07-11 19:41:05 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 20:40:27 +00:00
|
|
|
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx); \
|
|
|
|
|
if (value == NULL) { \
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx); \
|
2022-04-21 20:40:27 +00:00
|
|
|
} \
|
|
|
|
|
\
|
|
|
|
|
struct apfl_value new_value = {.type = TYPE}; \
|
|
|
|
|
if ((new_value.MEMB = NEW) == NULL) { \
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = apfl_stack_drop(ctx, -1); \
|
|
|
|
|
assert(ok); \
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx); \
|
2022-04-21 20:40:27 +00:00
|
|
|
} \
|
|
|
|
|
\
|
2022-06-24 21:13:44 +00:00
|
|
|
*value = new_value;
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_nil(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_NIL,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_bool(apfl_ctx ctx, bool b)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_BOOLEAN,
|
|
|
|
|
.boolean = b,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_number(apfl_ctx ctx, apfl_number num)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_NUMBER,
|
|
|
|
|
.number = num,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_STRING,
|
|
|
|
|
string,
|
2023-03-05 21:55:20 +00:00
|
|
|
apfl_string_copy_to_new_gc_string(&ctx->gc, sv)
|
2022-04-22 19:39:03 +00:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
static bool
|
|
|
|
|
try_push_const_string(apfl_ctx ctx, const char *string)
|
2022-04-23 20:14:42 +00:00
|
|
|
{
|
|
|
|
|
return apfl_stack_push(ctx, (struct apfl_value) {
|
|
|
|
|
.type = VALUE_CONST_STRING,
|
|
|
|
|
.const_string = apfl_string_view_from(string),
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-23 20:14:42 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
|
|
|
|
apfl_push_const_string(apfl_ctx ctx, const char *string)
|
|
|
|
|
{
|
|
|
|
|
if (!try_push_const_string(ctx, string)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
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)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
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) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (list_val->type != VALUE_LIST) {
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(
|
|
|
|
|
ctx,
|
|
|
|
|
"Can not append to value of type {value:type}, expected list",
|
|
|
|
|
*list_val
|
|
|
|
|
);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, value_index);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok;
|
|
|
|
|
|
2023-10-23 20:41:26 +00:00
|
|
|
if (!apfl_list_splice_raw(
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
&ctx->gc,
|
|
|
|
|
&list_val->list,
|
|
|
|
|
list_val->list->len,
|
|
|
|
|
0,
|
|
|
|
|
&value,
|
|
|
|
|
1
|
2022-06-24 21:13:44 +00:00
|
|
|
)) {
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop(ctx, value_index);
|
|
|
|
|
assert(ok);
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop(ctx, value_index);
|
|
|
|
|
assert(ok);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
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) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dst_val->type != VALUE_LIST) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value src_val = apfl_stack_must_get(ctx, src_index);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok;
|
2022-04-24 14:45:25 +00:00
|
|
|
if (src_val.type != VALUE_LIST) {
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop(ctx, src_index);
|
|
|
|
|
assert(ok);
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2023-10-23 20:41:26 +00:00
|
|
|
if (!apfl_list_splice_raw(
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
&ctx->gc,
|
|
|
|
|
&dst_val->list,
|
|
|
|
|
dst_val->list->len,
|
|
|
|
|
0,
|
|
|
|
|
src_val.list->items,
|
|
|
|
|
src_val.list->len
|
2022-06-24 21:13:44 +00:00
|
|
|
)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop(ctx, src_index);
|
|
|
|
|
assert(ok);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_dict_create(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_DICT,
|
|
|
|
|
dict,
|
|
|
|
|
apfl_dict_new(&ctx->gc)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
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
|
|
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok;
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
if (dict_value->type != VALUE_DICT) {
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index});
|
|
|
|
|
assert(ok);
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_dict);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) {
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index});
|
|
|
|
|
assert(ok);
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index});
|
|
|
|
|
assert(ok);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-22 22:54:03 +00:00
|
|
|
static void
|
|
|
|
|
must_tmproot_add_value(apfl_ctx ctx, struct apfl_value value)
|
|
|
|
|
{
|
|
|
|
|
if (!apfl_value_add_as_tmproot(&ctx->gc, value)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
push_pair_inner(apfl_ctx ctx, apfl_stackidx l_index, apfl_stackidx r_index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value l = apfl_stack_must_get(ctx, l_index);
|
|
|
|
|
struct apfl_value r = apfl_stack_must_get(ctx, r_index);
|
|
|
|
|
|
|
|
|
|
must_tmproot_add_value(ctx, l);
|
|
|
|
|
must_tmproot_add_value(ctx, r);
|
|
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){l_index, r_index});
|
|
|
|
|
assert(ok);
|
2023-03-22 22:54:03 +00:00
|
|
|
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_PAIR,
|
|
|
|
|
pair,
|
|
|
|
|
apfl_pair_new(&ctx->gc, l, r)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_push_pair(apfl_ctx ctx, apfl_stackidx l, apfl_stackidx r)
|
|
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
|
push_pair_inner(ctx, l, r);
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-23 22:38:35 +00:00
|
|
|
void
|
|
|
|
|
apfl_push_csymbol(apfl_ctx ctx, apfl_cfunc id, const char *name)
|
|
|
|
|
{
|
|
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
|
|
|
.type = VALUE_CSYMBOL,
|
|
|
|
|
.csymbol = {
|
|
|
|
|
.id = id,
|
|
|
|
|
.name = name,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-03 21:39:29 +00:00
|
|
|
apfl_cfunc
|
|
|
|
|
apfl_pop_csymbol(apfl_ctx ctx, apfl_stackidx idx)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value val = apfl_stack_must_pop(ctx, idx);
|
|
|
|
|
|
|
|
|
|
return val.type == VALUE_CSYMBOL
|
|
|
|
|
? val.csymbol.id
|
|
|
|
|
: NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-23 22:38:35 +00:00
|
|
|
static void
|
|
|
|
|
push_symbol_inner(apfl_ctx ctx, apfl_stackidx idx)
|
|
|
|
|
{
|
|
|
|
|
apfl_move_to_top_of_stack(ctx, idx);
|
|
|
|
|
if (apfl_get_type(ctx, -1) != APFL_VALUE_STRING) {
|
|
|
|
|
apfl_raise_errorfmt(ctx, "Expected string, got {stack:type}", -1);
|
|
|
|
|
}
|
|
|
|
|
struct apfl_string *name = apfl_to_dynamic_string(ctx, -1);
|
|
|
|
|
|
|
|
|
|
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(name, GC_TYPE_STRING))) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_stack_drop(ctx, -1);
|
|
|
|
|
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_SYMBOL,
|
|
|
|
|
symbol,
|
|
|
|
|
apfl_symbol_new(&ctx->gc, name)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_push_symbol(apfl_ctx ctx, apfl_stackidx idx)
|
|
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
|
push_symbol_inner(ctx, idx);
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_push_anon_symbol(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_SYMBOL,
|
|
|
|
|
symbol,
|
|
|
|
|
apfl_symbol_new(&ctx->gc, NULL)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
bool
|
|
|
|
|
apfl_get_member_if_exists(
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx container_index,
|
|
|
|
|
apfl_stackidx k_index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value container;
|
|
|
|
|
struct apfl_value k;
|
|
|
|
|
if (
|
2022-07-11 19:41:05 +00:00
|
|
|
!current_stack_get_and_adjust_index(ctx, &container, &container_index)
|
|
|
|
|
|| !current_stack_get_and_adjust_index(ctx, &k, &k_index)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok;
|
2022-06-24 21:13:44 +00:00
|
|
|
if (value == NULL) {
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index});
|
|
|
|
|
assert(ok);
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum get_item_result result = apfl_value_get_item(container, k, value);
|
|
|
|
|
if (result != GET_ITEM_OK) {
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop(ctx, -1); // Drop the placeholder
|
|
|
|
|
assert(ok);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index});
|
|
|
|
|
assert(ok);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
case GET_ITEM_OK:
|
2022-11-19 21:06:23 +00:00
|
|
|
return true;
|
2022-06-24 21:13:44 +00:00
|
|
|
case GET_ITEM_KEY_DOESNT_EXIST:
|
2022-11-19 21:06:23 +00:00
|
|
|
return false;
|
2022-06-24 21:13:44 +00:00
|
|
|
case GET_ITEM_NOT_A_CONTAINER:
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(
|
|
|
|
|
ctx,
|
|
|
|
|
"Can not get member of value of type {value:type}, not a container",
|
|
|
|
|
container
|
|
|
|
|
);
|
2022-06-24 21:13:44 +00:00
|
|
|
break;
|
|
|
|
|
case GET_ITEM_WRONG_KEY_TYPE:
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(
|
|
|
|
|
ctx,
|
|
|
|
|
"Value of type {value:type} is not a valid key for container type {value:type}",
|
|
|
|
|
k,
|
|
|
|
|
container
|
|
|
|
|
);
|
2022-06-24 21:13:44 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2022-11-19 21:06:23 +00:00
|
|
|
|
|
|
|
|
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)) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
2022-11-19 21:06:23 +00:00
|
|
|
}
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
|
|
|
|
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) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index >= list.list->len) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]);
|
2022-11-29 22:13:10 +00:00
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
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) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
2022-11-29 22:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index_in_list >= list->list->len) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
2022-11-29 22:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, value_index);
|
|
|
|
|
|
2023-10-23 20:41:26 +00:00
|
|
|
if (!apfl_list_splice_raw(
|
2022-11-29 22:13:10 +00:00
|
|
|
&ctx->gc,
|
|
|
|
|
&list->list,
|
|
|
|
|
index_in_list,
|
|
|
|
|
1,
|
|
|
|
|
&value,
|
|
|
|
|
1
|
|
|
|
|
)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_drop(ctx, value_index);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
2023-10-23 20:41:26 +00:00
|
|
|
void
|
|
|
|
|
apfl_list_splice(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx list,
|
|
|
|
|
size_t cut_off,
|
|
|
|
|
size_t cut_count,
|
|
|
|
|
apfl_stackidx other,
|
|
|
|
|
size_t other_off,
|
|
|
|
|
size_t other_count
|
|
|
|
|
) {
|
|
|
|
|
apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]) {other, list});
|
|
|
|
|
|
|
|
|
|
struct apfl_value *list_val = stack_get_pointer(ctx, -1);
|
|
|
|
|
struct apfl_value *other_val = stack_get_pointer(ctx, -2);
|
|
|
|
|
|
|
|
|
|
if (list_val->type != VALUE_LIST) {
|
|
|
|
|
apfl_raise_errorfmt(ctx, "Expected list, got {value:type}", *list_val);
|
|
|
|
|
}
|
|
|
|
|
struct list_header** list_listp = &list_val->list;
|
|
|
|
|
if (other_val->type != VALUE_LIST) {
|
|
|
|
|
apfl_raise_errorfmt(ctx, "Expected list, got {value:type}", *other_val);
|
|
|
|
|
}
|
|
|
|
|
struct list_header* other_list = other_val->list;
|
|
|
|
|
|
|
|
|
|
if (!apfl_resizable_check_cut_args((*list_listp)->len, cut_off, cut_count)) {
|
|
|
|
|
apfl_raise_const_error(ctx, apfl_messages.splice_arguments_out_of_range);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value *other_values = NULL;
|
|
|
|
|
if (other_count > 0) {
|
|
|
|
|
if (
|
|
|
|
|
other_off >= other_list->len
|
|
|
|
|
|| other_count > (other_list->len - other_off)
|
|
|
|
|
) {
|
|
|
|
|
apfl_raise_const_error(ctx, apfl_messages.splice_arguments_out_of_range);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
other_values = &(other_list->items[other_off]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_list_splice_raw(
|
|
|
|
|
&ctx->gc,
|
|
|
|
|
list_listp,
|
|
|
|
|
cut_off,
|
|
|
|
|
cut_count,
|
|
|
|
|
other_values,
|
|
|
|
|
other_count
|
|
|
|
|
)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_drop(ctx, -2);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
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:
|
2023-03-22 22:54:03 +00:00
|
|
|
case VALUE_PAIR:
|
2022-07-12 20:13:07 +00:00
|
|
|
case VALUE_FUNC:
|
|
|
|
|
case VALUE_CFUNC:
|
|
|
|
|
case VALUE_USERDATA:
|
2023-01-29 15:46:00 +00:00
|
|
|
case VALUE_NATIVE_OBJECT:
|
2023-03-23 22:38:35 +00:00
|
|
|
case VALUE_SYMBOL:
|
|
|
|
|
case VALUE_CSYMBOL:
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(
|
|
|
|
|
ctx,
|
|
|
|
|
"Can not get length of value of type {value:type}",
|
|
|
|
|
value
|
|
|
|
|
);
|
2022-07-12 20:13:07 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 20:37:37 +00:00
|
|
|
static void
|
|
|
|
|
iterate_dict_inner(apfl_ctx ctx, apfl_stackidx dict_index, void *opaque, bool (*callback)(apfl_ctx, void *))
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value dict_value = apfl_value_set_cow_flag(apfl_stack_must_pop(ctx, dict_index));
|
|
|
|
|
if (!apfl_value_add_as_tmproot(&ctx->gc, dict_value)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dict_value.type != VALUE_DICT) {
|
|
|
|
|
apfl_raise_errorfmt(ctx, "Expected dict value, got {value:type}", dict_value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HASHMAP_EACH(&(dict_value.dict->map), it) {
|
|
|
|
|
struct apfl_value *k = apfl_hashmap_cursor_peek_key(it);
|
|
|
|
|
apfl_stack_must_push(ctx, *k);
|
|
|
|
|
struct apfl_value *v = apfl_hashmap_cursor_peek_value(it);
|
|
|
|
|
apfl_stack_must_push(ctx, *v);
|
|
|
|
|
if (!callback(ctx, opaque)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_iterate_dict(apfl_ctx ctx, apfl_stackidx dict, void *opaque, bool (*it)(apfl_ctx, void *))
|
|
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
|
iterate_dict_inner(ctx, dict, opaque, it);
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
|
|
|
}
|
2023-03-22 22:54:03 +00:00
|
|
|
|
2022-11-20 15:26:38 +00:00
|
|
|
static bool
|
|
|
|
|
get_string_view_of_value(struct apfl_string_view *sv, struct apfl_value value)
|
2022-07-12 20:13:07 +00:00
|
|
|
{
|
|
|
|
|
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:
|
2023-03-22 22:54:03 +00:00
|
|
|
case VALUE_PAIR:
|
2023-01-29 15:46:00 +00:00
|
|
|
case VALUE_NATIVE_OBJECT:
|
2023-03-23 22:38:35 +00:00
|
|
|
case VALUE_SYMBOL:
|
|
|
|
|
case VALUE_CSYMBOL:
|
2022-11-20 15:26:38 +00:00
|
|
|
return false;
|
2022-07-12 20:13:07 +00:00
|
|
|
case VALUE_STRING:
|
2022-11-20 15:26:38 +00:00
|
|
|
*sv = apfl_string_view_from(*value.string);
|
|
|
|
|
return true;
|
2022-07-12 20:13:07 +00:00
|
|
|
case VALUE_CONST_STRING:
|
2022-11-20 15:26:38 +00:00
|
|
|
*sv = value.const_string;
|
|
|
|
|
return true;
|
2022-07-12 20:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
2022-11-20 15:26:38 +00:00
|
|
|
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))) {
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(ctx, "Expected string, got {stack:type}", index);
|
2022-11-20 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
return sv;
|
2022-07-12 20:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-12 21:24:19 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 21:44:34 +00:00
|
|
|
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);
|
2023-02-10 20:38:54 +00:00
|
|
|
if (!apfl_value_format(value, apfl_io_string_writer(&sb))) {
|
2022-10-30 21:44:34 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-30 21:50:01 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
void
|
|
|
|
|
apfl_join_strings(apfl_ctx ctx, apfl_stackidx glue, apfl_stackidx parts)
|
2022-11-20 15:26:38 +00:00
|
|
|
{
|
2022-11-26 22:06:55 +00:00
|
|
|
apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]){parts, glue});
|
|
|
|
|
parts = -2;
|
|
|
|
|
glue = -1;
|
2022-11-20 15:26:38 +00:00
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
if (apfl_get_type(ctx, parts) != APFL_VALUE_LIST) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
2022-11-20 15:26:38 +00:00
|
|
|
}
|
2022-11-29 22:13:10 +00:00
|
|
|
size_t parts_len = apfl_len(ctx, parts);
|
2022-11-20 15:26:38 +00:00
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
apfl_tostring(ctx, glue);
|
|
|
|
|
struct apfl_string_view glue_sv = apfl_get_string(ctx, glue);
|
2022-11-20 15:26:38 +00:00
|
|
|
|
|
|
|
|
size_t total_length = 0;
|
|
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
for (size_t i = 0; i < parts_len; i++) {
|
2022-11-26 22:06:55 +00:00
|
|
|
// Convert values to strings, if necessary
|
2022-11-29 22:13:10 +00:00
|
|
|
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 {
|
2022-11-26 22:06:55 +00:00
|
|
|
apfl_tostring(ctx, -1);
|
2022-11-29 22:13:10 +00:00
|
|
|
total_length += apfl_get_string(ctx, -1).len;
|
|
|
|
|
apfl_set_list_member_by_index(ctx, -3, i, -1);
|
2022-11-26 22:06:55 +00:00
|
|
|
}
|
2022-11-20 15:26:38 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
for (size_t i = 0; i < parts_len; i++) {
|
2022-11-20 15:26:38 +00:00
|
|
|
if (i > 0) {
|
|
|
|
|
if (!apfl_string_builder_append(&sb, glue_sv)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
apfl_get_list_member_by_index(ctx, parts, i);
|
2022-11-20 15:26:38 +00:00
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
if (!apfl_string_builder_append(&sb, apfl_get_string(ctx, -1))) {
|
2022-11-20 15:26:38 +00:00
|
|
|
goto error;
|
|
|
|
|
}
|
2022-11-29 22:13:10 +00:00
|
|
|
|
|
|
|
|
apfl_drop(ctx, -1);
|
2022-11-20 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
// Drop the glue and list from the stack
|
|
|
|
|
apfl_drop(ctx, -2);
|
|
|
|
|
apfl_drop(ctx, -2);
|
2022-11-20 15:26:38 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
error:
|
2022-11-26 22:06:55 +00:00
|
|
|
// Drop the glue and list from the stack
|
|
|
|
|
apfl_drop(ctx, -1);
|
|
|
|
|
apfl_drop(ctx, -1);
|
2022-11-20 15:26:38 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 15:46:00 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 21:24:19 +00:00
|
|
|
bool
|
|
|
|
|
apfl_is_truthy(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
2022-10-31 14:51:28 +00:00
|
|
|
struct apfl_value value = apfl_stack_must_pop(ctx, index);
|
2022-07-12 21:24:19 +00:00
|
|
|
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;
|
|
|
|
|
}
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(ctx, "Expected number, got {value:type}", value);
|
2022-07-12 21:24:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b});
|
|
|
|
|
assert(ok);
|
2022-07-12 21:24:19 +00:00
|
|
|
return eq;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-20 12:47:38 +00:00
|
|
|
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);
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b});
|
|
|
|
|
assert(ok);
|
2022-11-20 12:47:38 +00:00
|
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
case CMP_LT:
|
|
|
|
|
return -1;
|
|
|
|
|
case CMP_EQ:
|
|
|
|
|
return 0;
|
|
|
|
|
case CMP_GT:
|
|
|
|
|
return 1;
|
|
|
|
|
case CMP_UNCOMPARABLE:
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(ctx, "Values of type {value:type} can not be compared", a);
|
2022-11-20 12:47:38 +00:00
|
|
|
case CMP_INCOMPATIBLE_TYPES:
|
2023-01-24 20:22:22 +00:00
|
|
|
apfl_raise_errorfmt(ctx, "Can not compare values of incompatible types {value:type} and {value:type}", a, b);
|
2022-11-20 12:47:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static struct scope *
|
2022-11-19 21:06:23 +00:00
|
|
|
closure_scope_for_func_inner(apfl_ctx ctx, struct scopes scopes)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
// 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
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
if (scopes.closure != NULL) {
|
|
|
|
|
if (!apfl_scope_merge_into(out, scopes.closure)) {
|
2022-07-11 19:41:05 +00:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
if (scopes.local != NULL) {
|
|
|
|
|
if (!apfl_scope_merge_into(out, scopes.local)) {
|
2022-07-11 19:41:05 +00:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct scope *
|
2022-11-19 21:06:23 +00:00
|
|
|
apfl_closure_scope_for_func(apfl_ctx ctx, struct scopes scopes)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
2022-11-19 21:06:23 +00:00
|
|
|
struct scope *out = closure_scope_for_func_inner(ctx, scopes);
|
2022-07-11 19:41:05 +00:00
|
|
|
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)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
void
|
|
|
|
|
apfl_set_func_name(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx name_index)
|
|
|
|
|
{
|
|
|
|
|
apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx []) {func_index, name_index});
|
|
|
|
|
if (apfl_get_type(ctx, -1) != APFL_VALUE_STRING) {
|
|
|
|
|
apfl_raise_errorfmt(ctx, "Expected string, got {stack:type}", -1);
|
|
|
|
|
}
|
|
|
|
|
struct apfl_value func = apfl_stack_must_get(ctx, -2);
|
|
|
|
|
struct apfl_string *name = apfl_to_dynamic_string(ctx, -1);
|
|
|
|
|
switch (func.type) {
|
|
|
|
|
case VALUE_FUNC:
|
|
|
|
|
func.func->name = name;
|
|
|
|
|
break;
|
|
|
|
|
case VALUE_CFUNC:
|
|
|
|
|
func.cfunc->name = name;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
apfl_raise_errorfmt(ctx, "Expected function, got {value:type}", func);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_drop(ctx, -1);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-16 19:52:27 +00:00
|
|
|
static noreturn void
|
2022-07-11 19:41:05 +00:00
|
|
|
raise_no_cfunc(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_c_function);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-26 20:22:34 +00:00
|
|
|
if (entry->type != APFL_CSE_CFUNCTION) {
|
2022-07-11 19:41:05 +00:00
|
|
|
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) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.invalid_slotidx);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
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) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.wrong_type);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
return value.userdata;
|
|
|
|
|
}
|
2022-10-28 19:32:17 +00:00
|
|
|
|
2023-01-29 15:46:00 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 20:38:54 +00:00
|
|
|
struct apfl_io_writer apfl_get_output_writer(apfl_ctx ctx)
|
2022-10-28 19:32:17 +00:00
|
|
|
{
|
|
|
|
|
return ctx->output_writer;
|
|
|
|
|
}
|
2022-11-19 21:06:23 +00:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-26 20:22:34 +00:00
|
|
|
|
|
|
|
|
size_t
|
|
|
|
|
apfl_call_stack_depth(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
return ctx->call_stack.len;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-30 21:50:01 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-26 20:22:34 +00:00
|
|
|
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, },
|
2023-01-30 21:50:01 +00:00
|
|
|
.filename = (struct apfl_string_view) { .len = 0, .bytes = NULL, },
|
2023-01-26 20:22:34 +00:00
|
|
|
.toplevel = false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (cse->type) {
|
|
|
|
|
case APFL_CSE_FUNCTION:
|
|
|
|
|
info.line_current = cse->func.execution_line;
|
2023-01-30 21:50:01 +00:00
|
|
|
info.filename = get_string_view_or_empty(cse->func.instructions->filename);
|
2023-01-26 20:22:34 +00:00
|
|
|
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:
|
2023-01-30 21:50:01 +00:00
|
|
|
info.name = get_string_view_or_empty(cse->cfunc.func->name);
|
2023-01-26 20:22:34 +00:00
|
|
|
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;
|
2023-01-30 21:50:01 +00:00
|
|
|
info.filename = get_string_view_or_empty(cse->func_dispatch.function->filename);
|
2023-01-26 20:22:34 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case APFL_CSE_MATCHER:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-30 21:50:01 +00:00
|
|
|
static bool
|
|
|
|
|
format_defined_at(
|
2023-02-10 20:38:54 +00:00
|
|
|
struct apfl_io_writer w,
|
2023-01-30 21:50:01 +00:00
|
|
|
struct apfl_call_stack_entry_info info,
|
|
|
|
|
struct apfl_string_view *filename
|
|
|
|
|
) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "defined in "));
|
2023-01-30 21:50:01 +00:00
|
|
|
if (filename != NULL && filename->len > 0) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "file "));
|
|
|
|
|
FMT_TRY(apfl_io_write_string(w, *filename));
|
|
|
|
|
FMT_TRY(apfl_io_write_string(w, ", "));
|
2023-01-30 21:50:01 +00:00
|
|
|
}
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "line "));
|
2023-02-16 20:41:02 +00:00
|
|
|
FMT_TRY(apfl_format_put_int(w, (int)info.line_defined));
|
2023-01-26 20:22:34 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2023-02-10 20:38:54 +00:00
|
|
|
apfl_call_stack_entry_info_format(struct apfl_io_writer w, struct apfl_call_stack_entry_info info)
|
2023-01-26 20:22:34 +00:00
|
|
|
{
|
|
|
|
|
switch (info.type) {
|
|
|
|
|
case APFL_CSE_FUNCTION:
|
2023-01-30 21:50:01 +00:00
|
|
|
if (info.filename.len > 0) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "File "));
|
|
|
|
|
FMT_TRY(apfl_io_write_string(w, info.filename));
|
|
|
|
|
FMT_TRY(apfl_io_write_string(w, ", "));
|
2023-01-30 21:50:01 +00:00
|
|
|
}
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "Line "));
|
2023-02-16 20:41:02 +00:00
|
|
|
FMT_TRY(apfl_format_put_int(w, (int)info.line_current));
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, ", "));
|
2023-01-26 20:22:34 +00:00
|
|
|
|
|
|
|
|
if (info.toplevel) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "toplevel "));
|
2023-01-26 20:22:34 +00:00
|
|
|
} else if (info.name.len == 0) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "anonymous function (subfunction "));
|
2023-01-26 20:22:34 +00:00
|
|
|
FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index));
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "; "));
|
2023-01-30 21:50:01 +00:00
|
|
|
FMT_TRY(format_defined_at(w, info, NULL));
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, ")"));
|
2023-01-26 20:22:34 +00:00
|
|
|
} else {
|
2023-02-10 20:38:54 +00:00
|
|
|
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 "));
|
2023-01-26 20:22:34 +00:00
|
|
|
FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index));
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "; "));
|
2023-01-30 21:50:01 +00:00
|
|
|
FMT_TRY(format_defined_at(w, info, NULL));
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, ")"));
|
2023-01-26 20:22:34 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case APFL_CSE_CFUNCTION:
|
|
|
|
|
if (info.name.len == 0) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "Anonymous native function"));
|
2023-01-26 20:22:34 +00:00
|
|
|
} else {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "Native function "));
|
|
|
|
|
FMT_TRY(apfl_io_write_string(w, info.name));
|
2023-01-26 20:22:34 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case APFL_CSE_FUNCTION_DISPATCH:
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "Dispatch for "));
|
2023-01-26 20:22:34 +00:00
|
|
|
if (info.name.len == 0) {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "anonymous function ("));
|
2023-01-26 20:22:34 +00:00
|
|
|
} else {
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "function "));
|
|
|
|
|
FMT_TRY(apfl_io_write_string(w, info.name));
|
|
|
|
|
FMT_TRY(apfl_io_write_string(w, "("));
|
2023-01-26 20:22:34 +00:00
|
|
|
}
|
2023-01-30 21:50:01 +00:00
|
|
|
FMT_TRY(format_defined_at(w, info, &info.filename));
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, ")"));
|
2023-01-26 20:22:34 +00:00
|
|
|
break;
|
|
|
|
|
case APFL_CSE_MATCHER:
|
2023-02-10 20:38:54 +00:00
|
|
|
FMT_TRY(apfl_io_write_string(w, "Matcher"));
|
2023-01-26 20:22:34 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2023-01-30 21:50:01 +00:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-05 21:55:20 +00:00
|
|
|
struct apfl_error err;
|
|
|
|
|
if (!apfl_compile_whole_file(
|
|
|
|
|
&ctx->gc,
|
|
|
|
|
parser,
|
|
|
|
|
&err,
|
|
|
|
|
ilist
|
|
|
|
|
)) {
|
|
|
|
|
apfl_drop(ctx, -1);
|
|
|
|
|
apfl_parser_destroy(parser);
|
|
|
|
|
apfl_tokenizer_destroy(tokenizer);
|
|
|
|
|
apfl_raise_error_object(ctx, err);
|
2023-01-30 21:50:01 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-03-05 16:02:42 +00:00
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
load_bytecode_inner(apfl_ctx ctx, struct apfl_io_reader r)
|
|
|
|
|
{
|
|
|
|
|
struct instruction_list *ilist = apfl_bytecode_unserialize(&ctx->gc, r);
|
|
|
|
|
if (ilist == NULL) {
|
|
|
|
|
apfl_raise_const_error(ctx, "Failed to load bytecode");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))) {
|
|
|
|
|
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,
|
|
|
|
|
1,
|
|
|
|
|
NULL,
|
|
|
|
|
ilist->line,
|
|
|
|
|
ilist->filename
|
|
|
|
|
)) == NULL) {
|
|
|
|
|
apfl_drop(ctx, -1);
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func_value->type = VALUE_FUNC;
|
|
|
|
|
|
2023-07-02 14:12:51 +00:00
|
|
|
bool ok = apfl_func_add_subfunc(func_value->func, ilist, NULL);
|
|
|
|
|
assert(ok /* should not fail, func was initialized with cap of 1 */);
|
2023-03-05 16:02:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_load_bytecode(apfl_ctx ctx, struct apfl_io_reader r)
|
|
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
|
load_bytecode_inner(ctx, r);
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
|
|
|
}
|
2023-07-03 21:39:29 +00:00
|
|
|
|
|
|
|
|
struct apfl_allocator
|
|
|
|
|
apfl_get_allocator(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
return ctx->gc.allocator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_cfunc_defer(apfl_ctx ctx, apfl_cfunc_defer_callback cb, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
|
|
|
|
|
if (cse == NULL || cse->type != APFL_CSE_CFUNCTION) {
|
|
|
|
|
apfl_raise_const_error(ctx, "apfl_cfunc_defer must be called from within a cfunc");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_resizable_append(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct cfunc_deferred),
|
|
|
|
|
(void **)&cse->cfunc.deferred_list,
|
|
|
|
|
&cse->cfunc.deferred_len,
|
|
|
|
|
&cse->cfunc.deferred_cap,
|
|
|
|
|
&(struct cfunc_deferred) {
|
|
|
|
|
.cb = cb,
|
|
|
|
|
.opaque = opaque,
|
|
|
|
|
},
|
|
|
|
|
1
|
|
|
|
|
)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_cfunc_run_deferred(apfl_ctx ctx, struct call_stack_entry *cse)
|
|
|
|
|
{
|
|
|
|
|
assert(cse != NULL);
|
|
|
|
|
assert(cse->type == APFL_CSE_CFUNCTION);
|
|
|
|
|
|
|
|
|
|
for (size_t i = cse->cfunc.deferred_len; i-- > 0; ) {
|
|
|
|
|
struct cfunc_deferred *deferred = &cse->cfunc.deferred_list[i];
|
|
|
|
|
deferred->cb(ctx, deferred->opaque);
|
|
|
|
|
}
|
|
|
|
|
}
|