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 :)
This commit is contained in:
Laria 2022-04-11 22:24:22 +02:00
parent 4088fdd1c3
commit 90a80152e1
24 changed files with 2331 additions and 1998 deletions

View file

@ -4,22 +4,31 @@ lib_LIBRARIES = libapfl.a
libapfl_a_SOURCES =
libapfl_a_SOURCES += alloc.c
libapfl_a_SOURCES += bytecode.c
libapfl_a_SOURCES += compile.c
libapfl_a_SOURCES += context.c
libapfl_a_SOURCES += error.c
libapfl_a_SOURCES += eval.c
libapfl_a_SOURCES += expr.c
libapfl_a_SOURCES += gc.c
libapfl_a_SOURCES += hashmap.c
libapfl_a_SOURCES += internal.c
libapfl_a_SOURCES += parser.c
libapfl_a_SOURCES += position.c
libapfl_a_SOURCES += resizable.c
libapfl_a_SOURCES += source_readers.c
libapfl_a_SOURCES += strings.c
libapfl_a_SOURCES += token.c
libapfl_a_SOURCES += tokenizer.c
libapfl_a_SOURCES += parser.c
libapfl_a_SOURCES += source_readers.c
libapfl_a_SOURCES += value.c
apfl_internal_headers =
apfl_internal_headers += hashmap.c
apfl_internal_headers += alloc.h
apfl_internal_headers += bytecode.h
apfl_internal_headers += compile.h
apfl_internal_headers += context.h
apfl_internal_headers += gc.h
apfl_internal_headers += hashmap.h
apfl_internal_headers += internal.h
apfl_internal_headers += resizable.h
apfl_internal_headers += value.h

View file

@ -62,10 +62,6 @@ struct apfl_string {
size_t cap;
};
struct apfl_refcounted_string_data;
typedef struct apfl_refcounted_string_data *apfl_refcounted_string;
#define APFL_STR_FMT "%.*s"
#define APFL_STR_FMT_ARGS(s) (int)(s).len,(s).bytes
@ -73,12 +69,10 @@ struct apfl_string_view apfl_string_view_from_view(struct apfl_string_view);
struct apfl_string_view apfl_string_view_from_cstr(char *);
struct apfl_string_view apfl_string_view_from_const_cstr(const char *);
struct apfl_string_view apfl_string_view_from_string(struct apfl_string);
struct apfl_string_view apfl_string_view_from_refcounted_string(apfl_refcounted_string);
#define apfl_string_view_from(s) _Generic((s), \
struct apfl_string: apfl_string_view_from_string, \
struct apfl_string_view: apfl_string_view_from_view, \
apfl_refcounted_string: apfl_string_view_from_refcounted_string, \
char *: apfl_string_view_from_cstr, \
const char *: apfl_string_view_from_const_cstr \
)(s)
@ -114,25 +108,6 @@ struct apfl_string apfl_string_builder_move_string(struct apfl_string_builder *)
#define apfl_string_builder_append_cstr(builder, cstr) (apfl_string_builder_append((builder), apfl_string_view_from_cstr((cstr))))
apfl_refcounted_string apfl_string_copy_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string_view);
apfl_refcounted_string apfl_string_move_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string *);
/* Increases the reference of the refcounted string.
* Returns the same apfl_refcounted_string value.
*/
apfl_refcounted_string apfl_refcounted_string_incref(apfl_refcounted_string);
/* Unrefs the refcounted string. It is no longer allowed to use the pointer
* after calling this function with it!
*/
void apfl_refcounted_string_unref(struct apfl_allocator allocator, apfl_refcounted_string );
/* Like apfl_refcounted_string_unref, but accepts a pointer to a refcounted_string.
* The pointed to value will be set to NULL.
*/
void apfl_refcounted_string_unref_ptr(struct apfl_allocator allocator, apfl_refcounted_string *);
// Tokens
enum apfl_token_type {
@ -164,7 +139,7 @@ struct apfl_token {
enum apfl_token_type type;
struct apfl_position position;
union {
apfl_refcounted_string text;
struct apfl_string text;
apfl_number number;
};
};
@ -200,6 +175,7 @@ enum apfl_error_type {
APFL_ERR_UNEXPECTED_CONSTANT_IN_MEMBER_ACCESS,
APFL_ERR_UNEXPECTED_EXPR_IN_MEMBER_ACCESS,
APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS,
APFL_ERR_NOT_IMPLEMENTED,
};
const char *apfl_error_type_name(enum apfl_error_type);
@ -282,7 +258,7 @@ struct apfl_expr_const {
union {
// variant nil is without data
bool boolean;
apfl_refcounted_string string;
struct apfl_string string;
apfl_number number;
};
};
@ -310,7 +286,7 @@ struct apfl_expr_param {
enum apfl_expr_param_type type;
union {
apfl_refcounted_string var;
struct apfl_string var;
struct apfl_expr_const constant;
struct apfl_expr_param_predicate predicate;
struct apfl_expr_params list;
@ -350,7 +326,7 @@ enum apfl_expr_assignable_var_or_member_type {
struct apfl_expr_assignable_var_or_member_dot {
struct apfl_expr_assignable_var_or_member *lhs;
apfl_refcounted_string rhs;
struct apfl_string rhs;
};
struct apfl_expr_assignable_var_or_member_at {
struct apfl_expr_assignable_var_or_member *lhs;
@ -361,7 +337,7 @@ struct apfl_expr_assignable_var_or_member {
enum apfl_expr_assignable_var_or_member_type type;
union {
apfl_refcounted_string var;
struct apfl_string var;
struct apfl_expr_assignable_var_or_member_dot dot;
struct apfl_expr_assignable_var_or_member_at at;
};
@ -400,7 +376,7 @@ struct apfl_expr_assignment {
struct apfl_expr_dot {
struct apfl_expr *lhs;
apfl_refcounted_string rhs;
struct apfl_string rhs;
};
struct apfl_expr_at {
@ -421,7 +397,7 @@ struct apfl_expr {
struct apfl_expr_dot dot;
struct apfl_expr_at at;
struct apfl_expr_const constant;
apfl_refcounted_string var;
struct apfl_string var;
// blank has no further data
};
@ -565,6 +541,16 @@ struct apfl_expr apfl_parser_get_expr(apfl_parser_ptr);
struct apfl_ctx_data;
typedef struct apfl_ctx_data *apfl_ctx;
enum apfl_value_type {
APFL_VALUE_NIL,
APFL_VALUE_BOOLEAN,
APFL_VALUE_NUMBER,
APFL_VALUE_STRING,
APFL_VALUE_LIST,
APFL_VALUE_DICT,
APFL_VALUE_FUNC,
};
typedef int apfl_stackidx;
enum apfl_result {
@ -583,6 +569,22 @@ void apfl_ctx_destroy(apfl_ctx);
enum apfl_result apfl_eval(apfl_ctx, struct apfl_expr);
// Get the type of a value on the stack
enum apfl_value_type apfl_get_type(apfl_ctx, apfl_stackidx);
// Create a new empty list on the stack
enum apfl_result apfl_list_create(apfl_ctx, size_t initial_capacity);
// Append a value to a list. The value will be dropped.
enum apfl_result apfl_list_append(apfl_ctx, apfl_stackidx list, apfl_stackidx value);
// Append a list to another list. The second list will be dropped.
enum apfl_result apfl_list_append_list(apfl_ctx, apfl_stackidx a, apfl_stackidx b);
// Create a new empty dict on the stack
enum apfl_result apfl_dict_create(apfl_ctx);
// Set a value in a dictionary. k and v will be dropped.
enum apfl_result apfl_dict_set(apfl_ctx, apfl_stackidx dict, apfl_stackidx k, apfl_stackidx v);
// Get a value from a container (list or dict) and push it on the stack. container and k will be dropped.
enum apfl_result apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k);
void apfl_debug_print_val(apfl_ctx, apfl_stackidx, FILE *);
#ifdef __cplusplus

111
src/bytecode.c Normal file
View file

@ -0,0 +1,111 @@
#include <assert.h>
#include "apfl.h"
#include "alloc.h"
#include "bytecode.h"
#include "gc.h"
struct instruction_list *
apfl_instructions_new(struct gc *gc, int line)
{
struct instruction_list *ilist = apfl_gc_new_instructions(gc);
if (ilist == NULL) {
return NULL;
}
*ilist = (struct instruction_list) {
.instructions = NULL,
.len = 0,
.cap = 0,
.line = line,
};
return ilist;
}
void
apfl_instructions_deinit(struct apfl_allocator allocator, struct instruction_list *ilist)
{
FREE_LIST(allocator, ilist->instructions, ilist->cap);
}
#define GET_ARGUMENT(ilist, i, arg) \
do { \
if (i >= ilist->len) { \
return; \
} \
arg = ilist->instructions[++i]; \
} while (0)
void
apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, void *opaque)
{
union instruction_or_arg arg;
for (size_t i = 0; i < ilist->len; i++) {
switch (ilist->instructions[i].instruction) {
case INSN_NIL:
case INSN_TRUE:
case INSN_FALSE:
case INSN_LIST_APPEND:
case INSN_LIST_EXPAND_INTO:
case INSN_DICT:
case INSN_DICT_APPEND_KVPAIR:
case INSN_GET_MEMBER:
case INSN_NEXT_LINE:
break;
case INSN_NUMBER:
case INSN_LIST:
case INSN_SET_LINE:
i++;
break;
case INSN_STRING:
case INSN_VAR_GET:
case INSN_VAR_SET:
case INSN_VAR_NEW:
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING));
return;
}
}
}
const char *
apfl_instruction_to_string(enum instruction insn)
{
switch (insn) {
case INSN_NIL:
return "INSN_NIL";
case INSN_TRUE:
return "INSN_TRUE";
case INSN_FALSE:
return "INSN_FALSE";
case INSN_NUMBER:
return "INSN_NUMBER";
case INSN_STRING:
return "INSN_STRING";
case INSN_LIST:
return "INSN_LIST";
case INSN_LIST_APPEND:
return "INSN_LIST_APPEND";
case INSN_LIST_EXPAND_INTO:
return "INSN_LIST_EXPAND_INTO";
case INSN_DICT:
return "INSN_DICT";
case INSN_DICT_APPEND_KVPAIR:
return "INSN_DICT_APPEND_KVPAIR";
case INSN_GET_MEMBER:
return "INSN_GET_MEMBER";
case INSN_VAR_GET:
return "INSN_VAR_GET";
case INSN_VAR_SET:
return "INSN_VAR_SET";
case INSN_VAR_NEW:
return "INSN_VAR_NEW";
case INSN_NEXT_LINE:
return "INSN_NEXT_LINE";
case INSN_SET_LINE:
return "INSN_SET_LINE";
}
return "??";
}

57
src/bytecode.h Normal file
View file

@ -0,0 +1,57 @@
#ifndef APFL_BYTECODE_H
#define APFL_BYTECODE_H
#ifdef __cplusplus
extern "C" {
#endif
#include "apfl.h"
#include "gc.h"
enum instruction {
INSN_NIL, // ( -- nil)
INSN_TRUE, // ( -- true)
INSN_FALSE, // ( -- false)
INSN_NUMBER, // ( -- number), arg: number
INSN_STRING, // ( -- string), arg: string
INSN_LIST, // ( -- list), arg: count (preallocation hint)
INSN_LIST_APPEND, // ( list val -- list' )
INSN_LIST_EXPAND_INTO, // ( list list -- list' )
INSN_DICT, // ( -- dict )
INSN_DICT_APPEND_KVPAIR, // ( dict key value -- dict' )
INSN_GET_MEMBER, // ( list/dict key -- value )
INSN_VAR_GET, // ( -- value ), arg: string
INSN_VAR_SET, // ( value -- ), arg: string
INSN_VAR_NEW, // ( -- ), arg: string
INSN_NEXT_LINE, // ( -- )
INSN_SET_LINE, // ( -- ), arg: count (new line number)
};
union instruction_or_arg {
enum instruction instruction;
struct apfl_string *string;
apfl_number number;
size_t count;
};
struct instruction_list {
union instruction_or_arg *instructions;
size_t len;
size_t cap;
int line;
};
const char *apfl_instruction_to_string(enum instruction);
struct instruction_list *apfl_instructions_new(struct gc *, int line);
void apfl_instructions_deinit(struct apfl_allocator, struct instruction_list *);
void apfl_gc_instructions_traverse(struct instruction_list *, gc_visitor, void *);
#ifdef __cplusplus
}
#endif
#endif

349
src/compile.c Normal file
View file

@ -0,0 +1,349 @@
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "apfl.h"
#include "compile.h"
#include "bytecode.h"
#include "resizable.h"
#define DEBUG_COMPILING 1
#if DEBUG_COMPILING
# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__);
#else
# define DEBUG_LOG(...)
#endif
struct compiler {
struct gc *gc;
struct apfl_error error;
size_t line;
};
#define TRY(b) do { if (!(b)) { return false; } } while(0)
#define MALLOC_FAIL_IF_NULL(compiler, x) \
do { \
if ((x) == NULL) { \
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); \
return false; \
} \
} while (0)
static bool compile_expr(struct compiler *, struct apfl_expr *, struct instruction_list *);
static bool
malloc_failure_on_false(struct compiler *compiler, bool b)
{
if (!b) {
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
}
return b;
}
static bool
ilist_ensure_cap(struct compiler *compiler, struct instruction_list *ilist, size_t n)
{
return malloc_failure_on_false(compiler, apfl_resizable_ensure_cap_for_more_elements(
compiler->gc->allocator,
sizeof(union instruction_or_arg),
(void **)&ilist->instructions,
ilist->len,
&ilist->cap,
n
));
}
static void
ilist_put_insn(struct instruction_list *ilist, enum instruction instruction)
{
assert(ilist->cap > 0);
assert(ilist->len < ilist->cap);
ilist->instructions[ilist->len] = (union instruction_or_arg) {
.instruction = instruction,
};
ilist->len++;
DEBUG_LOG("put_insn: %s\n", apfl_instruction_to_string(instruction));
}
static void
ilist_put_number(struct instruction_list *ilist, apfl_number number)
{
assert(ilist->cap > 0);
assert(ilist->len < ilist->cap);
ilist->instructions[ilist->len] = (union instruction_or_arg) {
.number = number
};
ilist->len++;
DEBUG_LOG("put_number: %lf\n", number);
}
static void
ilist_put_count(struct instruction_list *ilist, size_t count)
{
assert(ilist->cap > 0);
assert(ilist->len < ilist->cap);
ilist->instructions[ilist->len] = (union instruction_or_arg) {
.count = count
};
ilist->len++;
DEBUG_LOG("put_count: %d\n", (int)count);
}
static void
ilist_put_string(struct instruction_list *ilist, struct apfl_string *string)
{
assert(ilist->cap > 0);
assert(ilist->len < ilist->cap);
ilist->instructions[ilist->len] = (union instruction_or_arg) {
.string = string
};
ilist->len++;
DEBUG_LOG("put_string: " APFL_STR_FMT "\n", APFL_STR_FMT_ARGS(*string));
}
static bool
string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in)
{
struct apfl_string *str = apfl_gc_new_string(compiler->gc);
if (str == NULL) {
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
return false;
}
*str = apfl_string_move(in);
*out = str;
return true;
}
static bool
compile_constant(struct compiler *compiler, struct apfl_expr_const *constant, struct instruction_list *ilist)
{
switch (constant->type) {
case APFL_EXPR_CONST_NIL:
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_NIL);
return true;
case APFL_EXPR_CONST_BOOLEAN:
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, constant->boolean ? INSN_TRUE : INSN_FALSE);
return true;
case APFL_EXPR_CONST_NUMBER:
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_NUMBER);
ilist_put_number(ilist, constant->number);
return true;
case APFL_EXPR_CONST_STRING:
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct apfl_string *str;
TRY(string_move_into_new(compiler, &str, &constant->string));
ilist_put_insn(ilist, INSN_STRING);
ilist_put_string(ilist, str);
return true;
}
assert(false);
return false;
}
static bool
compile_list(struct compiler *compiler, struct apfl_expr_list *list, struct instruction_list *ilist)
{
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_LIST);
ilist_put_count(ilist, list->len);
for (size_t i = 0; i < list->len; i++) {
struct apfl_expr_list_item *item = &list->items[i];
TRY(compile_expr(compiler, item->expr, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, item->expand ? INSN_LIST_EXPAND_INTO : INSN_LIST_APPEND);
}
return true;
}
static bool
compile_dict(struct compiler *compiler, struct apfl_expr_dict *dict, struct instruction_list *ilist)
{
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_DICT);
for (size_t i = 0; i < dict->len; i++) {
struct apfl_expr_dict_pair *pair = &dict->items[i];
TRY(compile_expr(compiler, pair->k, ilist));
TRY(compile_expr(compiler, pair->v, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_DICT_APPEND_KVPAIR);
}
return true;
}
static bool
compile_dot(struct compiler *compiler, struct apfl_expr_dot *dot, struct instruction_list *ilist)
{
TRY(compile_expr(compiler, dot->lhs, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 3));
struct apfl_string *str;
TRY(string_move_into_new(compiler, &str, &dot->rhs));
ilist_put_insn(ilist, INSN_STRING);
ilist_put_string(ilist, str);
ilist_put_insn(ilist, INSN_GET_MEMBER);
return true;
}
static bool
compile_at(struct compiler *compiler, struct apfl_expr_at *at, struct instruction_list *ilist)
{
TRY(compile_expr(compiler, at->lhs, ilist));
TRY(compile_expr(compiler, at->rhs, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_GET_MEMBER);
return true;
}
static bool
compile_var(struct compiler *compiler, struct apfl_string *var, struct instruction_list *ilist)
{
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct apfl_string *str;
TRY(string_move_into_new(compiler, &str, var));
ilist_put_insn(ilist, INSN_VAR_GET);
ilist_put_string(ilist, str);
return true;
}
static bool
compile_simple_assignment(
struct compiler *compiler,
struct apfl_string *var,
struct apfl_expr *rhs,
struct instruction_list *ilist
) {
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct apfl_string *str;
TRY(string_move_into_new(compiler, &str, var));
ilist_put_insn(ilist, INSN_VAR_NEW);
ilist_put_string(ilist, str);
TRY(compile_expr(compiler, rhs, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_VAR_SET);
ilist_put_string(ilist, str);
return true;
}
static bool
compile_assignment(
struct compiler *compiler,
struct apfl_expr_assignment *assignment,
struct instruction_list *ilist
) {
if (
assignment->lhs.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER
&& assignment->lhs.var_or_member.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR
) {
return compile_simple_assignment(
compiler,
&assignment->lhs.var_or_member.var,
assignment->rhs,
ilist
);
}
// TODO: Implement other assignables
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
return false;
}
static bool
compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instruction_list *ilist)
{
size_t new_line = (size_t)expr->position.line;
if (new_line != compiler->line) {
if (new_line == compiler->line + 1) {
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_NEXT_LINE);
} else {
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_SET_LINE);
ilist_put_count(ilist, new_line);
}
compiler->line = new_line;
}
switch (expr->type) {
case APFL_EXPR_CALL:
case APFL_EXPR_SIMPLE_FUNC:
case APFL_EXPR_COMPLEX_FUNC:
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
return false;
case APFL_EXPR_CONSTANT:
return compile_constant(compiler, &expr->constant, ilist);
case APFL_EXPR_BLANK:
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_NIL);
return true;
case APFL_EXPR_LIST:
return compile_list(compiler, &expr->list, ilist);
case APFL_EXPR_DICT:
return compile_dict(compiler, &expr->dict, ilist);
case APFL_EXPR_DOT:
return compile_dot(compiler, &expr->dot, ilist);
case APFL_EXPR_AT:
return compile_at(compiler, &expr->at, ilist);
case APFL_EXPR_VAR:
return compile_var(compiler, &expr->var, ilist);
case APFL_EXPR_ASSIGNMENT:
return compile_assignment(compiler, &expr->assignment, ilist);
}
assert(false);
return false;
}
bool
apfl_compile(struct gc *gc, struct apfl_expr expr, struct apfl_error *error_out, struct instruction_list *out)
{
struct compiler compiler = {
.gc = gc,
.line = out->line,
};
bool ok = compile_expr(&compiler, &expr, out);
apfl_expr_deinit(gc->allocator, &expr);
if (!ok) {
*error_out = compiler.error;
}
return ok;
}

19
src/compile.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef APFL_COMPILE_H
#define APFL_COMPILE_H
#ifdef __cplusplus
extern "C" {
#endif
#include "apfl.h"
#include "bytecode.h"
#include "gc.h"
bool apfl_compile(struct gc *, struct apfl_expr, struct apfl_error *, struct instruction_list *);
#ifdef __cplusplus
}
#endif
#endif

550
src/context.c Normal file
View file

@ -0,0 +1,550 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "apfl.h"
#include "alloc.h"
#include "context.h"
#include "gc.h"
#include "hashmap.h"
#include "resizable.h"
#include "value.h"
typedef struct apfl_value *variable;
static struct stack *
stack_new(struct gc *gc)
{
struct stack *stack = apfl_gc_new_stack(gc);
if (stack == NULL) {
return NULL;
}
*stack = (struct stack) {
.items = NULL,
.len = 0,
.cap = 0,
};
return stack;
}
void
apfl_stack_deinit(struct apfl_allocator allocator, struct stack *stack)
{
FREE_LIST(allocator, stack->items, stack->cap);
}
bool
apfl_stack_push(apfl_ctx ctx, struct apfl_value value)
{
return apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct apfl_value),
(void **)&ctx->stack->items,
&ctx->stack->len,
&ctx->stack->cap,
&value,
1
);
}
bool
apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index)
{
if (*index < 0) {
if ((size_t)-*index > ctx->stack->len) {
return false;
}
*index = ctx->stack->len + *index;
} else if ((size_t)*index >= ctx->stack->len) {
return false;
}
assert(0 <= *index && (size_t)*index < ctx->stack->len);
return true;
}
static int
cmp_stackidx(const void *_a, const void *_b)
{
const apfl_stackidx *a = _a;
const apfl_stackidx *b = _b;
return *a - *b;
}
bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
{
for (size_t i = 0; i < count; i++) {
if (!apfl_stack_check_index(ctx, &indices[i])) {
return false;
}
}
qsort(indices, count, sizeof(apfl_stackidx), cmp_stackidx);
for (size_t i = count; i-- > 0; ) {
// Will not fail, as we've already checked the indices
assert(apfl_resizable_cut_without_resize(
sizeof(struct apfl_value),
(void **)&ctx->stack->items,
&ctx->stack->len,
indices[i],
1
));
}
// TODO: Shrink stack
return true;
}
bool
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
{
if (!apfl_stack_check_index(ctx, &index)) {
return false;
}
*value = ctx->stack->items[index];
assert(apfl_resizable_splice(
ctx->gc.allocator,
sizeof(struct apfl_value),
(void **)ctx->stack,
&ctx->stack->len,
&ctx->stack->cap,
index,
1,
NULL,
0
));
return true;
}
static struct apfl_value *
stack_get_pointer(apfl_ctx ctx, apfl_stackidx index)
{
if (!apfl_stack_check_index(ctx, &index)) {
return NULL;
}
return &ctx->stack->items[index];
}
static bool
stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index)
{
if (!apfl_stack_check_index(ctx, index)) {
return false;
}
*value = ctx->stack->items[*index];
return true;
}
bool
apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
{
return stack_get_and_adjust_index(ctx, value, &index);
}
static struct apfl_value *
stack_push_placeholder(apfl_ctx ctx)
{
if (!apfl_stack_push(ctx, (struct apfl_value) {.type = VALUE_NIL})) {
return NULL;
}
return stack_get_pointer(ctx, -1);
}
bool
apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value;
return apfl_stack_pop(ctx, &value, index);
}
static bool
scope_keys_eq(void *opaque, const void *_a, const void *_b)
{
(void)opaque;
const struct apfl_string * const *a = _a;
const struct apfl_string * const *b = _b;
return apfl_string_eq(**a, **b);
}
static apfl_hash
scope_calc_hash(void *opaque, const void *_key)
{
(void)opaque;
const struct apfl_string *const*key = _key;
struct apfl_string_view sv = apfl_string_view_from(**key);
return apfl_hash_fnv1a(sv.bytes, sv.len);
}
static struct scope *
scope_new(struct gc *gc)
{
struct apfl_hashmap map;
if (!apfl_hashmap_init(
&map,
gc->allocator,
(struct apfl_hashmap_callbacks) {
.opaque = NULL,
.keys_eq = scope_keys_eq,
.calc_hash = scope_calc_hash,
},
sizeof(struct apfl_string *),
sizeof(variable)
)) {
return NULL;
}
struct scope *scope = apfl_gc_new_scope(gc);
if (scope == NULL) {
apfl_hashmap_deinit(&map);
return NULL;
}
*scope = (struct scope) {
.map = map,
};
return scope;
}
void
apfl_scope_deinit(struct apfl_allocator allocator, struct scope *scope)
{
(void)allocator;
apfl_hashmap_deinit(&scope->map);
}
void
apfl_gc_var_traverse(struct apfl_value *var, gc_visitor cb, void *opaque)
{
struct gc_object *child = apfl_value_get_gc_object(*var);
if (child != NULL) {
cb(opaque, child);
}
}
void
apfl_gc_scope_traverse(struct scope *scope, gc_visitor cb, void *opaque)
{
HASHMAP_EACH(&scope->map, cur) {
struct apfl_string **k;
apfl_hashmap_cursor_peek_key(cur, (void **)&k);
cb(opaque, GC_OBJECT_FROM(*k, GC_TYPE_STRING));
variable *v;
apfl_hashmap_cursor_peek_value(cur, (void **)&v);
cb(opaque, GC_OBJECT_FROM(*v, GC_TYPE_VAR));
}
}
bool
apfl_scope_get(struct scope *scope, struct apfl_string *name, struct apfl_value *out)
{
variable var;
if (!apfl_hashmap_get(&scope->map, &name, &var)) {
return false;
}
// The value is now in the variable and outside of it (presumably to be
// saved onto the stack). We need to set the COW flag so a mutation of one
// copy doesn't affect the other one.
*out = apfl_value_set_cow_flag(*var);
return true;
}
static variable
get_or_create_variable(struct gc *gc, struct scope *scope, struct apfl_string *name)
{
variable var;
if (apfl_hashmap_get(&scope->map, &name, &var)) {
return var;
}
struct apfl_hashmap_prepared_set prepared_set;
if (!apfl_hashmap_prepare_set(&scope->map, &prepared_set, &name)) {
return NULL;
}
var = apfl_gc_new_var(gc);
if (var == NULL) {
return NULL;
}
*var = (struct apfl_value) { .type = VALUE_NIL };
apfl_hashmap_set_prepared(prepared_set, &var);
return var;
}
bool
apfl_scope_set(struct gc *gc, struct scope *scope, struct apfl_string *name, struct apfl_value value)
{
variable var = get_or_create_variable(gc, scope, name);
if (var == NULL) {
return false;
}
*var = value;
return true;
}
bool
apfl_scope_create_var(struct gc *gc, struct scope *scope, struct apfl_string *name)
{
return get_or_create_variable(gc, scope, name) != NULL;
}
apfl_ctx
apfl_ctx_new(struct apfl_allocator base_allocator)
{
apfl_ctx ctx = ALLOC_OBJ(base_allocator, struct apfl_ctx_data);
if (ctx == NULL) {
return NULL;
}
if (!apfl_gc_init(&ctx->gc, base_allocator)) {
FREE_OBJ(base_allocator, ctx);
return NULL;
}
if ((ctx->scope = scope_new(&ctx->gc)) == NULL) {
goto error;
}
if (!apfl_gc_root_add(&ctx->gc, GC_OBJECT_FROM(ctx->scope, GC_TYPE_SCOPE))) {
goto error;
}
if ((ctx->stack = stack_new(&ctx->gc)) == NULL) {
goto error;
}
if (!apfl_gc_root_add(&ctx->gc, GC_OBJECT_FROM(ctx->stack, GC_TYPE_STACK))) {
goto error;
}
return ctx;
error:
apfl_ctx_destroy(ctx);
return NULL;
}
void
apfl_ctx_destroy(apfl_ctx ctx)
{
if (ctx == NULL) {
return;
}
struct apfl_allocator base_allocator = ctx->gc.base_allocator;
apfl_gc_full(&ctx->gc);
apfl_gc_deinit(&ctx->gc);
FREE_OBJ(base_allocator, ctx);
}
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
struct apfl_value *value = stack_push_placeholder(ctx); \
if (value == NULL) { \
return APFL_RESULT_ERR_FATAL; \
} \
\
struct apfl_value new_value = {.type = TYPE}; \
if ((new_value.MEMB = NEW) == NULL) { \
assert(apfl_stack_drop(ctx, -1)); \
return APFL_RESULT_ERR_FATAL; \
} \
\
*value = new_value; \
\
return APFL_RESULT_OK;
enum apfl_result
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)
)
}
enum apfl_result
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) {
return APFL_RESULT_ERR;
}
if (list_val->type != VALUE_LIST) {
return APFL_RESULT_ERR;
}
struct apfl_value value;
if (!apfl_stack_get(ctx, &value, value_index)) {
return APFL_RESULT_ERR;
}
enum apfl_result result = apfl_list_splice(
&ctx->gc,
&list_val->list,
list_val->list->len,
0,
&value,
1
)
? APFL_RESULT_OK
: APFL_RESULT_ERR;
assert(apfl_stack_drop(ctx, value_index));
return result;
}
enum apfl_result
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) {
return APFL_RESULT_ERR;
}
if (dst_val->type != VALUE_LIST) {
return APFL_RESULT_ERR;
}
struct apfl_value src_val;
if (!apfl_stack_get(ctx, &src_val, src_index)) {
return APFL_RESULT_ERR;
}
if (dst_val->type != VALUE_LIST) {
assert(apfl_stack_drop(ctx, src_index));
return APFL_RESULT_ERR;
}
enum apfl_result result = apfl_list_splice(
&ctx->gc,
&dst_val->list,
dst_val->list->len,
0,
src_val.list->items,
src_val.list->len
)
? APFL_RESULT_OK
: APFL_RESULT_ERR;
assert(apfl_stack_drop(ctx, src_index));
return result;
}
enum apfl_result
apfl_dict_create(apfl_ctx ctx)
{
CREATE_GC_OBJECT_VALUE_ON_STACK(
ctx,
VALUE_DICT,
dict,
apfl_dict_new(&ctx->gc)
)
}
enum apfl_result
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
) {
return APFL_RESULT_ERR;
}
if (dict_value->type != VALUE_DICT) {
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
return APFL_RESULT_ERR;
}
if (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) {
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
return APFL_RESULT_ERR;
}
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
return APFL_RESULT_OK;
}
static enum apfl_result
apfl_get_member_inner(
apfl_ctx ctx,
struct apfl_value container,
struct apfl_value k
) {
struct apfl_value *value = stack_push_placeholder(ctx);
if (value == NULL) {
return APFL_RESULT_ERR;
}
if (apfl_value_get_item(container, k, value) != GET_ITEM_OK) {
assert(apfl_stack_drop(ctx, -1));
return APFL_RESULT_ERR;
}
return APFL_RESULT_OK;
}
enum apfl_result
apfl_get_member(
apfl_ctx ctx,
apfl_stackidx container_index,
apfl_stackidx k_index
) {
struct apfl_value container;
struct apfl_value k;
if (
!stack_get_and_adjust_index(ctx, &container, &container_index)
|| !stack_get_and_adjust_index(ctx, &k, &k_index)
) {
return APFL_RESULT_ERR;
}
enum apfl_result result = apfl_get_member_inner(ctx, container, k);
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
return result;
}
void
apfl_gc_stack_traverse(struct stack *stack, gc_visitor visitor, void *opaque)
{
for (size_t i = 0; i < stack->len; i++) {
struct gc_object *object = apfl_value_get_gc_object(stack->items[i]);
if (object != NULL) {
visitor(opaque, object);
}
}
}

57
src/context.h Normal file
View file

@ -0,0 +1,57 @@
#ifndef APFL_CONTEXT_H
#define APFL_CONTEXT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "bytecode.h"
#include "hashmap.h"
#include "gc.h"
#include "value.h"
struct stack {
struct apfl_value *items;
size_t len;
size_t cap;
};
struct scope {
struct apfl_hashmap map;
};
struct apfl_ctx_data {
struct gc gc;
struct scope *scope;
struct stack *stack;
int execution_line;
};
void apfl_stack_deinit(struct apfl_allocator, struct stack *);
void apfl_gc_stack_traverse(struct stack *, gc_visitor, void *);
void apfl_scope_deinit(struct apfl_allocator, struct scope *);
bool apfl_scope_get(struct scope *, struct apfl_string *name, struct apfl_value *out);
bool apfl_scope_set(struct gc *, struct scope *, struct apfl_string *name, struct apfl_value value);
bool apfl_scope_create_var(struct gc *, struct scope *, struct apfl_string *name);
void apfl_gc_var_traverse(struct apfl_value *, gc_visitor, void *);
void apfl_gc_scope_traverse(struct scope *, gc_visitor, void *);
bool apfl_stack_push(apfl_ctx, struct apfl_value);
bool apfl_stack_check_index(apfl_ctx, apfl_stackidx *);
bool apfl_stack_pop(apfl_ctx, struct apfl_value *value, apfl_stackidx);
bool apfl_stack_get(apfl_ctx, struct apfl_value *value, apfl_stackidx);
bool apfl_stack_drop(apfl_ctx, apfl_stackidx);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -54,6 +54,8 @@ apfl_error_type_name(enum apfl_error_type type)
return "APFL_ERR_UNEXPECTED_EXPR_IN_MEMBER_ACCESS";
case APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS:
return "APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS";
case APFL_ERR_NOT_IMPLEMENTED:
return "APFL_ERR_NOT_IMPLEMENTED";
}
return "<unknown error>";
@ -177,6 +179,11 @@ apfl_error_print(struct apfl_error error, FILE *file)
POSARGS
);
break;
case APFL_ERR_NOT_IMPLEMENTED:
fprintf(
file,
"Feature not implemented\n"
);
}
fprintf(file, "Unknown error %d\n", (int)error.type);

1455
src/eval.c

File diff suppressed because it is too large Load diff

View file

@ -40,8 +40,7 @@ apfl_expr_deinit(struct apfl_allocator allocator, struct apfl_expr *expr)
apfl_expr_const_deinit(allocator, &expr->constant);
break;
case APFL_EXPR_VAR:
apfl_refcounted_string_unref(allocator, expr->var);
expr->var = NULL;
apfl_string_deinit(allocator, &expr->var);
break;
case APFL_EXPR_BLANK:
// nop
@ -100,8 +99,7 @@ apfl_expr_const_deinit(struct apfl_allocator allocator, struct apfl_expr_const *
// nop
break;
case APFL_EXPR_CONST_STRING:
apfl_refcounted_string_unref(allocator, constant->string);
constant->string = NULL;
apfl_string_deinit(allocator, &constant->string);
break;
}
}
@ -135,8 +133,7 @@ apfl_expr_param_deinit(struct apfl_allocator allocator, struct apfl_expr_param *
{
switch (param->type) {
case APFL_EXPR_PARAM_VAR:
apfl_refcounted_string_unref(allocator, param->var);
param->var = NULL;
apfl_string_deinit(allocator, &param->var);
break;
case APFL_EXPR_PARAM_CONSTANT:
apfl_expr_const_deinit(allocator, &param->constant);
@ -189,8 +186,7 @@ void
apfl_expr_assignable_var_or_member_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member_dot *dot)
{
DESTROY(allocator, dot->lhs, apfl_expr_assignable_var_or_member_deinit);
apfl_refcounted_string_unref(allocator, dot->rhs);
dot->rhs = NULL;
apfl_string_deinit(allocator, &dot->rhs);
}
void
@ -205,8 +201,7 @@ apfl_expr_assignable_var_or_member_deinit(struct apfl_allocator allocator, struc
{
switch (var_or_member->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
apfl_refcounted_string_unref(allocator, var_or_member->var);
var_or_member->var = NULL;
apfl_string_deinit(allocator, &var_or_member->var);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
apfl_expr_assignable_var_or_member_dot_deinit(allocator, &var_or_member->dot);
@ -250,8 +245,7 @@ void
apfl_expr_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_dot *dot)
{
DESTROY(allocator, dot->lhs, apfl_expr_deinit);
apfl_refcounted_string_unref(allocator, dot->rhs);
dot->rhs = NULL;
apfl_string_deinit(allocator, &dot->rhs);
}
void
@ -295,7 +289,7 @@ apfl_expr_move(struct apfl_expr *in)
out.constant = apfl_expr_const_move(&in->constant);
break;
case APFL_EXPR_VAR:
MOVEPTR(out.var, in->var);
out.var = apfl_string_move(&in->var);
break;
case APFL_EXPR_BLANK:
// nop
@ -384,7 +378,7 @@ apfl_expr_const_move(struct apfl_expr_const *in)
// nop
break;
case APFL_EXPR_CONST_STRING:
MOVEPTR(out.string,in->string);
out.string = apfl_string_move(&in->string);
}
return out;
@ -418,7 +412,7 @@ apfl_expr_param_move(struct apfl_expr_param *in)
struct apfl_expr_param out = *in;
switch (in->type) {
case APFL_EXPR_PARAM_VAR:
MOVEPTR(out.var, in->var);
out.var = apfl_string_move(&in->var);
break;
case APFL_EXPR_PARAM_CONSTANT:
out.constant = apfl_expr_const_move(&in->constant);
@ -459,11 +453,11 @@ apfl_expr_assignable_var_or_member_move(struct apfl_expr_assignable_var_or_membe
struct apfl_expr_assignable_var_or_member out = *in;
switch (in->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
MOVEPTR(out.var, in->var);
out.var = apfl_string_move(&in->var);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
MOVEPTR(out.dot.lhs, in->dot.lhs);
MOVEPTR(out.dot.rhs, in->dot.rhs);
out.dot.rhs = apfl_string_move(&in->dot.rhs);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
MOVEPTR(out.at.lhs, in->at.lhs);
@ -528,7 +522,7 @@ apfl_expr_dot_move(struct apfl_expr_dot *in)
{
struct apfl_expr_dot out;
MOVEPTR(out.lhs, in->lhs);
MOVEPTR(out.rhs, in->rhs);
out.rhs = apfl_string_move(&in->rhs);
return out;
}

545
src/gc.c Normal file
View file

@ -0,0 +1,545 @@
#include <assert.h>
#include <stdio.h>
#include "alloc.h"
#include "bytecode.h"
#include "context.h"
#include "gc.h"
#include "hashmap.h"
#include "resizable.h"
#include "value.h"
#define DEBUG_GC 1
struct gc_object {
// Unlike most other tagged unions in apfl, the union is first here.
// This allows us to have pointers to the wrapped object that can be cast
// into gc_object pointers and vice versa.
union {
struct list_header list;
struct dict_header dict;
struct apfl_value var;
struct apfl_string string;
struct instruction_list instructions;
struct scope scope;
struct stack stack;
};
enum gc_type type;
enum gc_status status;
};
#define GC_OBJECTS_PER_BLOCK 128
struct gc_block {
struct gc_object objects[GC_OBJECTS_PER_BLOCK];
struct gc_block *next;
};
static void *
gc_allocator(void *opaque, void *oldptr, size_t oldsize, size_t newsize)
{
struct gc *gc = opaque;
void *out = ALLOCATOR_CALL(gc->base_allocator, oldptr, oldsize, newsize);
if (newsize != 0 && out == NULL) {
// We're out of memory! Try to get out of this situation by doing a full
// GC run.
apfl_gc_full(gc);
// Hopefully we now have memory again. Try the allocation again.
out = ALLOCATOR_CALL(gc->base_allocator, oldptr, oldsize, newsize);
}
if (newsize != 0 && out == NULL) {
return NULL;
}
// TODO: incremental GC step
return out;
}
struct gc_object *
apfl_gc_object_from_ptr(void *ptr, enum gc_type type)
{
struct gc_object *object = ptr;
assert(object->type == type);
return object;
}
bool
apfl_gc_init(struct gc *gc, struct apfl_allocator allocator)
{
gc->base_allocator = allocator;
gc->allocator = (struct apfl_allocator) {
.opaque = gc,
.alloc = gc_allocator,
};
gc->block = NULL;
gc->tmproots = (struct gc_tmproots) {
.roots = NULL,
.len = 0,
.cap = 0,
};
gc->tmproot_for_adding = NULL;
// TODO: It's a bit wasteful that we use a hashmap here. We are only
// ever interested in the keys.
return apfl_hashmap_init(
&gc->roots,
allocator,
(struct apfl_hashmap_callbacks) {.opaque = NULL},
sizeof(struct gc_object *),
sizeof(char)
);
}
static struct gc_block *
new_block(struct gc *gc)
{
struct gc_block *block = ALLOC_OBJ(gc->allocator, struct gc_block);
if (block == NULL) {
return NULL;
}
for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) {
block->objects[i] = (struct gc_object) { .status = GC_STATUS_FREE };
}
block->next = NULL;
return block;
}
static struct gc_object *
new_object_inner(struct gc *gc)
{
struct gc_block **cur = &gc->block;
while (*cur != NULL) {
struct gc_block *block = *cur;
for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) {
if (block->objects[i].status == GC_STATUS_FREE) {
return &block->objects[i];
}
}
cur = &block->next;
}
struct gc_block *nb = new_block(gc);
if (nb == NULL) {
return NULL;
}
*cur = nb;
return &nb->objects[0];
}
static struct gc_object *
new_object(struct gc *gc, enum gc_type type)
{
struct gc_object *object = new_object_inner(gc);
if (object == NULL) {
return NULL;
}
assert(object->status == GC_STATUS_FREE);
object->status = GC_STATUS_WHITE;
object->type = type;
return object;
}
#define IMPL_NEW(t, name, type, field) \
t * \
name(struct gc *gc) \
{ \
struct gc_object *object = new_object(gc, type); \
return object == NULL ? NULL : &object->field; \
}
IMPL_NEW(struct list_header, apfl_gc_new_list, GC_TYPE_LIST, list )
IMPL_NEW(struct dict_header, apfl_gc_new_dict, GC_TYPE_DICT, dict )
IMPL_NEW(struct apfl_value, apfl_gc_new_var, GC_TYPE_VAR, var )
IMPL_NEW(struct apfl_string, apfl_gc_new_string, GC_TYPE_STRING, string )
IMPL_NEW(struct instruction_list, apfl_gc_new_instructions, GC_TYPE_INSTRUCTIONS, instructions )
IMPL_NEW(struct scope, apfl_gc_new_scope, GC_TYPE_SCOPE, scope )
IMPL_NEW(struct stack, apfl_gc_new_stack, GC_TYPE_STACK, stack )
bool
apfl_gc_root_add(struct gc *gc, struct gc_object *object)
{
// Since setting the new root can trigger a garbage collection, we need to
// set the root as the tmproot_for_adding, so we'll treat it as a root and
// not free it.
assert(gc->tmproot_for_adding == NULL);
gc->tmproot_for_adding = object;
char v = 0;
bool ok = apfl_hashmap_set(&gc->roots, &object, &v);
gc->tmproot_for_adding = NULL;
return ok;
}
void
apfl_gc_root_remove(struct gc *gc, struct gc_object *object)
{
apfl_hashmap_delete(&gc->roots, &object);
}
size_t
apfl_gc_tmproots_begin(struct gc *gc)
{
return gc->tmproots.len;
}
void
apfl_gc_tmproots_restore(struct gc *gc, size_t newlen)
{
assert(newlen <= gc->tmproots.len);
gc->tmproots.len = newlen;
}
bool
apfl_gc_tmproot_add(struct gc *gc, struct gc_object *object)
{
// Since appending the new tmproot can trigger a garbage collection, we need
// to set the tmproot as the tmproot_for_adding, so we'll treat it as a root
// and not free it.
assert(gc->tmproot_for_adding == NULL);
gc->tmproot_for_adding = object;
bool ok = apfl_resizable_append(
gc->allocator,
sizeof(struct gc_object *),
(void **)&gc->tmproots.roots,
&gc->tmproots.len,
&gc->tmproots.cap,
&object,
1
);
gc->tmproot_for_adding = NULL;
return ok;
}
static void
color_object_grey(struct gc_object *object)
{
object->status = object->status == GC_STATUS_BLACK ? GC_STATUS_BLACK : GC_STATUS_GREY;
}
static void
visit_roots(struct gc *gc, gc_visitor visitor, void *opaque)
{
HASHMAP_EACH(&gc->roots, cur) {
struct gc_object *obj;
apfl_hashmap_cursor_get_key(cur, &obj);
visitor(opaque, obj);
}
for (size_t i = 0; i < gc->tmproots.len; i++) {
visitor(opaque, gc->tmproots.roots[i]);
}
if (gc->tmproot_for_adding != NULL) {
visitor(opaque, gc->tmproot_for_adding);
}
}
static void
mark_roots_visitor(void *opaque, struct gc_object *root)
{
(void)opaque;
color_object_grey(root);
}
static void
mark_roots(struct gc *gc)
{
visit_roots(gc, mark_roots_visitor, NULL);
}
static void
visit_children(struct gc_object *object, gc_visitor cb, void *opaque)
{
switch (object->type) {
case GC_TYPE_LIST:
apfl_gc_list_traverse(&object->list, cb, opaque);
return;
case GC_TYPE_DICT:
apfl_gc_dict_traverse(&object->dict, cb, opaque);
return;
case GC_TYPE_VAR:
apfl_gc_var_traverse(&object->var, cb, opaque);
return;
case GC_TYPE_SCOPE:
apfl_gc_scope_traverse(&object->scope, cb, opaque);
return;
case GC_TYPE_STRING:
// Intentionally left blank. Object doesn't reference other objects.
return;
case GC_TYPE_INSTRUCTIONS:
apfl_gc_instructions_traverse(&object->instructions, cb, opaque);
return;
case GC_TYPE_STACK:
apfl_gc_stack_traverse(&object->stack, cb, opaque);
return;
}
assert(false);
}
static void
trace_callback(void *opaque, struct gc_object *object)
{
(void)opaque;
color_object_grey(object);
}
static void
trace(struct gc_object *object)
{
object->status = GC_STATUS_BLACK;
visit_children(object, trace_callback, NULL);
}
static void
trace_while_having_grey(struct gc *gc)
{
bool found_grey;
do {
found_grey = false;
for (
struct gc_block *cur = gc->block;
cur != NULL;
cur = cur->next
) {
for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) {
struct gc_object *object = &cur->objects[i];
if (object->status == GC_STATUS_GREY) {
trace(object);
found_grey = true;
}
}
}
} while (found_grey);
}
static void
deinit_object(struct gc *gc, struct gc_object *object)
{
switch (object->type) {
case GC_TYPE_LIST:
apfl_list_deinit(gc->allocator, &object->list);
return;
case GC_TYPE_DICT:
apfl_dict_deinit(&object->dict);
return;
case GC_TYPE_VAR:
return;
case GC_TYPE_STRING:
apfl_string_deinit(gc->allocator, &object->string);
return;
case GC_TYPE_INSTRUCTIONS:
apfl_instructions_deinit(gc->allocator, &object->instructions);
return;
case GC_TYPE_SCOPE:
apfl_scope_deinit(gc->allocator, &object->scope);
return;
case GC_TYPE_STACK:
apfl_stack_deinit(gc->allocator, &object->stack);
return;
}
assert(false);
}
static void
sweep(struct gc *gc)
{
struct gc_block **cur = &gc->block;
while (*cur != NULL) {
struct gc_block *block = *cur;
bool completely_free = true;
for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) {
struct gc_object *object = &block->objects[i];
switch (object->status) {
case GC_STATUS_FREE:
break;
case GC_STATUS_WHITE:
deinit_object(gc, object);
object->status = GC_STATUS_FREE;
break;
case GC_STATUS_GREY:
assert(false /*Encountered grey object while sweeping*/);
break;
case GC_STATUS_BLACK:
object->status = GC_STATUS_WHITE; // Prepare for next run
completely_free = false;
break;
}
}
if (completely_free) {
*cur = block->next;
FREE_OBJ(gc->allocator, block);
} else {
cur = &block->next;
}
}
}
void
apfl_gc_full(struct gc *gc)
{
mark_roots(gc);
apfl_gc_debug_dump_graph(gc, stderr);
trace_while_having_grey(gc);
apfl_gc_debug_dump_graph(gc, stderr);
sweep(gc);
apfl_gc_debug_dump_graph(gc, stderr);
}
static const char *
dump_graph_bgcolor(enum gc_status status)
{
switch (status) {
case GC_STATUS_BLACK:
return "black";
case GC_STATUS_GREY:
return "grey";
default:
return "white";
}
}
static const char *
dump_graph_fgcolor(enum gc_status status)
{
switch (status) {
case GC_STATUS_BLACK:
return "white";
default:
return "black";
}
}
static const char *
type_to_string(enum gc_type type)
{
switch (type) {
case GC_TYPE_LIST:
return "list";
case GC_TYPE_DICT:
return "dict";
case GC_TYPE_VAR:
return "var";
case GC_TYPE_STRING:
return "string";
case GC_TYPE_INSTRUCTIONS:
return "instructions";
case GC_TYPE_SCOPE:
return "scope";
case GC_TYPE_STACK:
return "stack";
}
assert(false);
return "???";
}
static void
dump_graph_roots_visitor(void *opaque, struct gc_object *obj)
{
FILE *out = opaque;
fprintf(out, "ROOTS -> obj_%p;\n", (void *)obj);
}
struct dump_graph_visitor_data {
FILE *out;
struct gc_object *parent;
};
static void
dump_graph_visitor(void *opaque, struct gc_object *obj)
{
struct dump_graph_visitor_data *data = opaque;
fprintf(data->out, "obj_%p -> obj_%p\n", (void *)data->parent, (void *)obj);
}
void
apfl_gc_debug_dump_graph(struct gc *gc, FILE *out)
{
fprintf(out, "digraph G {\n");
visit_roots(gc, dump_graph_roots_visitor, out);
for (struct gc_block *block = gc->block; block != NULL; block = block->next) {
int counts[4] = {0, 0, 0, 0};
for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) {
struct gc_object *obj = &block->objects[i];
counts[obj->status]++;
if (obj->status == GC_STATUS_FREE) {
continue;
}
fprintf(out, "blk_%p -> obj_%p;\n", (void *)block, (void *)obj);
fprintf(
out,
"obj_%p [style=filled,fillcolor=%s,fontcolor=%s,label=\"Object %p\\ntype: %s\"];\n",
(void *)obj,
dump_graph_bgcolor(obj->status),
dump_graph_fgcolor(obj->status),
(void *)obj,
type_to_string(obj->type)
);
visit_children(obj, dump_graph_visitor, &(struct dump_graph_visitor_data) {
.out = out,
.parent = obj,
});
}
fprintf(out, "BLOCKS -> blk_%p;\n", (void *)block);
fprintf(
out,
"blk_%p [label=\"Block %p\\nfree %d, black %d, grey %d, white %d\"];\n",
(void *)block,
(void *)block,
counts[GC_STATUS_FREE],
counts[GC_STATUS_BLACK],
counts[GC_STATUS_GREY],
counts[GC_STATUS_WHITE]
);
}
fprintf(out, "}\n");
}
void
apfl_gc_deinit(struct gc *gc)
{
for (struct gc_block *block = gc->block; block != NULL; ) {
for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) {
struct gc_object *object = &block->objects[i];
if (object->status != GC_STATUS_FREE) {
deinit_object(gc, object);
}
}
struct gc_block *next = block->next;
FREE_OBJ(gc->allocator, block);
block = next;
}
gc->block = NULL;
FREE_LIST(gc->allocator, gc->tmproots.roots, gc->tmproots.cap);
apfl_hashmap_deinit(&gc->roots);
}

86
src/gc.h Normal file
View file

@ -0,0 +1,86 @@
#ifndef APFL_GC_H
#define APFL_GC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "apfl.h"
#include "hashmap.h"
struct gc_object;
enum gc_status {
GC_STATUS_FREE,
GC_STATUS_WHITE,
GC_STATUS_GREY,
GC_STATUS_BLACK,
};
enum gc_type {
GC_TYPE_LIST,
GC_TYPE_DICT,
GC_TYPE_VAR,
GC_TYPE_STRING,
GC_TYPE_INSTRUCTIONS,
GC_TYPE_SCOPE,
GC_TYPE_STACK,
};
struct gc_tmproots {
struct gc_object **roots;
size_t len;
size_t cap;
};
struct gc {
struct apfl_allocator base_allocator;
struct apfl_allocator allocator;
struct gc_block *block;
struct apfl_hashmap roots;
struct gc_tmproots tmproots;
struct gc_object *tmproot_for_adding;
};
typedef void (*gc_visitor)(void *, struct gc_object *);
#ifdef NDEBUG
# define GC_OBJECT_FROM(ptr, type) ((struct gc_object *)(ptr))
#else
# define GC_OBJECT_FROM(ptr, type) apfl_gc_object_from_ptr((ptr), (type))
#endif
struct gc_object *apfl_gc_object_from_ptr(void *, enum gc_type);
bool apfl_gc_init(struct gc *, struct apfl_allocator);
void apfl_gc_deinit(struct gc *);
void apfl_gc_debug_dump_graph(struct gc *, FILE *);
bool apfl_gc_root_add(struct gc *gc, struct gc_object *object);
void apfl_gc_root_remove(struct gc *gc, struct gc_object *object);
size_t apfl_gc_tmproots_begin(struct gc *gc);
void apfl_gc_tmproots_restore(struct gc *gc, size_t);
bool apfl_gc_tmproot_add(struct gc *gc, struct gc_object *object);
void apfl_gc_full(struct gc *gc);
struct list_header* apfl_gc_new_list(struct gc *);
struct dict_header* apfl_gc_new_dict(struct gc *);
struct apfl_value* apfl_gc_new_var(struct gc *);
struct apfl_string* apfl_gc_new_string(struct gc *);
struct instruction_list* apfl_gc_new_instructions(struct gc *);
struct scope* apfl_gc_new_scope(struct gc *);
struct stack* apfl_gc_new_stack(struct gc *);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -94,25 +94,6 @@ copy_value(const struct apfl_hashmap map, void *dest, void *src)
}
}
static bool
copy_opaque(const struct apfl_hashmap map, void **dest, void *src)
{
if (HAS_CALLBACK(map, copy_opaque)) {
return map.callbacks.copy_opaque(dest, src);
} else {
*dest = src;
return true;
}
}
static void
deinit_opaque(const struct apfl_hashmap map)
{
if (HAS_CALLBACK(map, deinit_opaque)) {
INVOKE_CALLBACK_NOARGS(map, deinit_opaque);
}
}
#define CAP_GROW 5
static_assert(CAP_GROW >= 1, "CAP_GROW must be at least 1");
@ -418,8 +399,6 @@ apfl_hashmap_deinit(struct apfl_hashmap *map)
FREE_LIST(map->allocator, map->buckets, map->nbuckets);
map->buckets = NULL;
}
deinit_opaque(*map);
}
struct apfl_hashmap
@ -438,12 +417,8 @@ apfl_hashmap_copy(struct apfl_hashmap *dst, struct apfl_hashmap src)
size_t valsize = src.valsize;
struct apfl_hashmap_callbacks dst_callbacks = src.callbacks;
if (!copy_opaque(src, &dst_callbacks.opaque, src.callbacks.opaque)) {
return false;
}
if (!hashmap_init(dst, src.allocator, dst_callbacks, src.nbuckets, keysize, valsize)) {
deinit_opaque(*dst);
return false;
}

View file

@ -44,12 +44,6 @@ struct apfl_hashmap_callbacks {
// Copies a value. Returns true on success, false on failure.
// If not provided, the bytes will be copied with memcpy.
void (*copy_value) (void *opaque, void *dest, void *src);
// Copies the opaque value. Returns true on success, false on failure.
bool (*copy_opaque) (void **dst, void *src);
// Called on deinitialization of the hashmap, if provided.
void (*deinit_opaque)(void *opaque);
};
struct apfl_hashmap {

View file

@ -49,7 +49,7 @@ enum fragment_type {
struct fragment_dot {
struct fragment *lhs;
apfl_refcounted_string rhs;
struct apfl_string rhs;
};
struct fragment_lhs_rhs {
@ -67,7 +67,7 @@ struct fragment {
union {
struct fragment *expand;
struct apfl_expr_const constant;
apfl_refcounted_string name;
struct apfl_string name;
struct fragment_dot dot;
struct fragment_lhs_rhs at;
struct fragment_lhs_rhs predicate;
@ -136,11 +136,11 @@ fragment_deinit(struct apfl_allocator allocator, struct fragment *fragment)
apfl_expr_const_deinit(allocator, &fragment->constant);
break;
case FRAG_NAME:
apfl_refcounted_string_unref_ptr(allocator, &fragment->name);
apfl_string_deinit(allocator, &fragment->name);
break;
case FRAG_DOT:
DESTROY(allocator, fragment->dot.lhs, fragment_deinit);
apfl_refcounted_string_unref_ptr(allocator, &fragment->dot.rhs);
apfl_string_deinit(allocator, &fragment->dot.rhs);
break;
case FRAG_AT:
deinit_fragment_lhs_rhs(allocator, &fragment->at);
@ -165,7 +165,7 @@ fragment_dot_move(struct fragment_dot *in)
{
struct fragment_dot out;
MOVEPTR(out.lhs, in->lhs);
MOVEPTR(out.rhs, in->rhs);
out.rhs = apfl_string_move(&in->rhs);
return out;
}
@ -201,7 +201,7 @@ fragment_move(struct fragment *in)
out.constant = apfl_expr_const_move(&in->constant);
break;
case FRAG_NAME:
MOVEPTR(out.name, in->name);
out.name = apfl_string_move(&in->name);
break;
case FRAG_DOT:
out.dot = fragment_dot_move(&in->dot);
@ -629,7 +629,7 @@ fragment_to_expr_inner(apfl_parser_ptr p, struct fragment *fragment, struct apfl
return true;
case FRAG_NAME:
out->type = APFL_EXPR_VAR;
out->var = apfl_refcounted_string_incref(fragment->name);
out->var = apfl_string_move(&fragment->name);
out->position = fragment->position;
return true;
case FRAG_DOT:
@ -637,7 +637,7 @@ fragment_to_expr_inner(apfl_parser_ptr p, struct fragment *fragment, struct apfl
if ((out->dot.lhs = fragment_to_expr_allocated(p, fragment_move(fragment->dot.lhs))) == NULL) {
return false;
}
out->dot.rhs = apfl_refcounted_string_incref(fragment->dot.rhs);
out->dot.rhs = apfl_string_move(&fragment->dot.rhs);
out->position = fragment->position;
return true;
case FRAG_AT:
@ -1001,7 +1001,7 @@ parse_stringify(apfl_parser_ptr p, struct fragment *fragment, struct apfl_positi
fragment->type = FRAG_CONSTANT;
fragment->constant = (struct apfl_expr_const) {
.type = APFL_EXPR_CONST_STRING,
.string = apfl_refcounted_string_incref(p->token.text),
.string = apfl_string_move(&p->token.text),
};
fragment->position = position;
return true;
@ -1070,7 +1070,7 @@ fragment_to_param_inner(
return true;
case FRAG_NAME:
out->type = APFL_EXPR_PARAM_VAR;
out->var = apfl_refcounted_string_incref(fragment->name);
out->var = apfl_string_move(&fragment->name);
return true;
case FRAG_DOT:
p->error = err_unexpected_token(APFL_TOK_DOT, fragment->position);
@ -1200,7 +1200,7 @@ static bool fragment_to_assignable_var_or_member(
return false;
case FRAG_NAME:
out->type = APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR,
out->var = apfl_refcounted_string_incref(fragment->name);
out->var = apfl_string_move(&fragment->name);
return true;
case FRAG_DOT:
lhs = ALLOC_OBJ(p->allocator, struct apfl_expr_assignable_var_or_member);
@ -1215,7 +1215,7 @@ static bool fragment_to_assignable_var_or_member(
out->type = APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT;
out->dot = (struct apfl_expr_assignable_var_or_member_dot) {
.lhs = lhs,
.rhs = apfl_refcounted_string_incref(fragment->dot.rhs),
.rhs = apfl_string_move(&fragment->dot.rhs),
};
return true;
case FRAG_AT:
@ -2039,7 +2039,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par
fragment->type = FRAG_BLANK;
} else {
fragment->type = FRAG_NAME;
fragment->name = apfl_refcounted_string_incref(p->token.text);
fragment->name = apfl_string_move(&p->token.text);
}
fragment->position = p->token.position;
break;
@ -2047,7 +2047,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par
fragment->type = FRAG_CONSTANT;
fragment->constant = (struct apfl_expr_const) {
.type = APFL_EXPR_CONST_STRING,
.string = apfl_refcounted_string_incref(p->token.text),
.string = apfl_string_move(&p->token.text),
};
fragment->position = p->token.position;
break;
@ -2084,7 +2084,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par
fragment->type = FRAG_DOT;
fragment->position = token_pos;
MOVEPTR(fragment->dot.lhs, lhs);
fragment->dot.rhs = apfl_refcounted_string_incref(p->token.text);
fragment->dot.rhs = apfl_string_move(&p->token.text);
break;
case APFL_TOK_AT:

View file

@ -114,11 +114,11 @@ expect_error_of_type(struct parser_test *pt, enum apfl_error_type want)
}
}
static apfl_refcounted_string
static struct apfl_string
new_string(struct parser_test *pt, const char *in)
{
apfl_refcounted_string out = apfl_string_copy_into_new_refcounted(pt->allocator, apfl_string_view_from(in));
if (out == NULL) {
struct apfl_string out = apfl_string_blank();
if (!apfl_string_copy(pt->allocator, &out, apfl_string_view_from(in))) {
test_fatalf(pt->t, "Failed copying string in new_string");
}
return out;

View file

@ -77,7 +77,51 @@ apfl_resizable_ensure_cap_for_more_elements(
return apfl_resizable_ensure_cap(allocator, elem_size, mem, cap, len + more_elements); // TODO: What if len + more_elements overflows?
}
bool apfl_resizable_splice(
bool
apfl_resizable_check_cut_args(size_t len, size_t cut_start, size_t cut_len)
{
return !(cut_start > len || cut_start + cut_len > len);
}
static void
move_elems_for_cut(
size_t elem_size,
void **mem,
size_t len,
size_t cut_start,
size_t cut_len,
size_t other_len
) {
size_t src_off = cut_start + cut_len;
size_t dst_off = cut_start + other_len;
memmove(
((char *)(*mem)) + (dst_off * elem_size),
((char *)(*mem)) + (src_off * elem_size),
(len - cut_start - cut_len) * elem_size
);
}
bool
apfl_resizable_cut_without_resize(
size_t elem_size,
void **mem,
size_t *len,
size_t cut_start,
size_t cut_len
) {
if (!apfl_resizable_check_cut_args(*len, cut_start, cut_len)) {
return false;
}
move_elems_for_cut(elem_size, mem, *len, cut_start, cut_len, 0);
*len -= cut_len;
return true;
}
bool
apfl_resizable_splice(
struct apfl_allocator allocator,
size_t elem_size,
void **mem,
@ -88,7 +132,7 @@ bool apfl_resizable_splice(
const void *other_mem,
size_t other_len
) {
if (cut_start > *len || cut_start + cut_len > *len) {
if (!apfl_resizable_check_cut_args(*len, cut_start, cut_len)) {
return false;
}
@ -105,14 +149,7 @@ bool apfl_resizable_splice(
}
}
size_t src_off = cut_start + cut_len;
size_t dst_off = cut_start + other_len;
memmove(
((char *)(*mem)) + (dst_off * elem_size),
((char *)(*mem)) + (src_off * elem_size),
(*len - cut_start - cut_len) * elem_size
);
move_elems_for_cut(elem_size, mem, *len, cut_start, cut_len, other_len);
if (other_len > 0 && other_mem != NULL) {
memcpy(

View file

@ -40,6 +40,16 @@ bool apfl_resizable_ensure_cap_for_more_elements(
size_t more_elements
);
bool apfl_resizable_check_cut_args(size_t len, size_t cut_start, size_t cut_len);
bool apfl_resizable_cut_without_resize(
size_t elem_size,
void **mem,
size_t *len,
size_t cut_start,
size_t cut_len
);
bool apfl_resizable_splice(
struct apfl_allocator,
size_t elem_size,

View file

@ -9,11 +9,6 @@
#include "internal.h"
#include "resizable.h"
struct apfl_refcounted_string_data {
unsigned refcount;
struct apfl_string string;
};
struct apfl_string_view
apfl_string_view_from_view(struct apfl_string_view view)
{
@ -158,75 +153,3 @@ apfl_string_builder_move_string(struct apfl_string_builder *builder)
return str;
}
struct apfl_string_view
apfl_string_view_from_refcounted_string(apfl_refcounted_string rcstring)
{
if (rcstring == NULL) {
return apfl_string_view_from(apfl_string_blank());
}
return apfl_string_view_from(rcstring->string);
}
apfl_refcounted_string
apfl_string_copy_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string_view sv)
{
struct apfl_string str = apfl_string_blank();
if (!apfl_string_copy(allocator, &str, sv)) {
return NULL;
}
apfl_refcounted_string rcstring = apfl_string_move_into_new_refcounted(allocator, &str);
if (rcstring == NULL) {
apfl_string_deinit(allocator, &str);
return NULL;
}
return rcstring;
}
apfl_refcounted_string
apfl_string_move_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string *src)
{
apfl_refcounted_string dst = ALLOC_OBJ(
allocator,
struct apfl_refcounted_string_data
);
if (dst == NULL) {
// TODO: Or should we free src here?
return NULL;
}
*dst = (struct apfl_refcounted_string_data) {
.refcount = 1,
.string = apfl_string_move(src),
};
return dst;
}
apfl_refcounted_string
apfl_refcounted_string_incref(apfl_refcounted_string rcstring)
{
if (rcstring != NULL) {
rcstring->refcount++;
}
return rcstring;
}
void
apfl_refcounted_string_unref(struct apfl_allocator allocator, apfl_refcounted_string rcstring)
{
if (rcstring != NULL && apfl_refcount_dec(&rcstring->refcount)) {
apfl_string_deinit(allocator, &rcstring->string);
FREE_OBJ(allocator, rcstring);
}
}
void
apfl_refcounted_string_unref_ptr(struct apfl_allocator allocator, apfl_refcounted_string *rcstring_ptr)
{
apfl_refcounted_string_unref(allocator, *rcstring_ptr);
*rcstring_ptr = NULL;
}

View file

@ -32,7 +32,7 @@ void
apfl_token_deinit(struct apfl_allocator allocator, struct apfl_token *token)
{
if (has_text_data(token->type)) {
apfl_refcounted_string_unref_ptr(allocator, &token->text);
apfl_string_deinit(allocator, &token->text);
}
}

View file

@ -260,21 +260,6 @@ apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer, bool need)
}
}
static apfl_refcounted_string
rcstring_from_string_builder(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder *builder)
{
struct apfl_string string = apfl_string_builder_move_string(builder);
apfl_refcounted_string rcstring = apfl_string_move_into_new_refcounted(
tokenizer->allocator,
&string
);
if (rcstring == NULL) {
apfl_string_deinit(tokenizer->allocator, &string);
tokenizer->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
}
return rcstring;
}
static enum apfl_parse_result
comment(apfl_tokenizer_ptr tokenizer)
{
@ -287,8 +272,6 @@ comment(apfl_tokenizer_ptr tokenizer)
apfl_string_builder_init(tokenizer->allocator, &text);
for (;;) {
apfl_refcounted_string rcstring;
last_pos = tokenizer->position;
switch (read_byte(tokenizer, &byte, true)) {
@ -297,16 +280,11 @@ comment(apfl_tokenizer_ptr tokenizer)
case RR_ERR:
return APFL_PARSE_ERROR;
case RR_EOF:
rcstring = rcstring_from_string_builder(tokenizer, &text);
if (rcstring == NULL) {
return APFL_PARSE_ERROR;
}
tokenizer->next_mode = NM_EOF;
tokenizer->token = (struct apfl_token) {
.type = APFL_TOK_COMMENT,
.position = pos,
.text = rcstring,
.text = apfl_string_builder_move_string(&text),
};
return APFL_PARSE_OK;
}
@ -314,15 +292,10 @@ comment(apfl_tokenizer_ptr tokenizer)
if (byte == '\n') {
unread_byte(tokenizer, last_pos);
rcstring = rcstring_from_string_builder(tokenizer, &text);
if (rcstring == NULL) {
return APFL_PARSE_ERROR;
}
tokenizer->token = (struct apfl_token) {
.type = APFL_TOK_COMMENT,
.position = pos,
.text = rcstring,
.text = apfl_string_builder_move_string(&text),
};
return APFL_PARSE_OK;
}
@ -595,19 +568,12 @@ inner_string(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder *text)
return APFL_PARSE_ERROR;
}
apfl_refcounted_string rcstring;
switch (byte) {
case '"':
rcstring = rcstring_from_string_builder(tokenizer, text);
if (rcstring == NULL) {
return APFL_PARSE_ERROR;
}
tokenizer->token = (struct apfl_token) {
.type = APFL_TOK_STRING,
.position = pos,
.text = rcstring,
.text = apfl_string_builder_move_string(text),
};
return APFL_PARSE_OK;
case '\\':
@ -651,15 +617,10 @@ finalize_maybe_name(
.position = pos,
};
} else {
apfl_refcounted_string rcstring = rcstring_from_string_builder(tokenizer, text);
if (rcstring == NULL) {
return APFL_PARSE_ERROR;
}
tokenizer->token = (struct apfl_token) {
.type = APFL_TOK_NAME,
.position = pos,
.text = rcstring,
.text = apfl_string_builder_move_string(text),
};
}

View file

@ -3,56 +3,18 @@
#include "apfl.h"
#include "alloc.h"
#include "context.h"
#include "internal.h"
#include "resizable.h"
#include "hashmap.h"
#include "value.h"
struct apfl_list_data {
unsigned refcount;
struct apfl_value *items;
size_t len;
size_t cap;
};
struct apfl_dict_data {
unsigned refcount;
struct apfl_hashmap map;
};
static apfl_hash value_hash(const struct apfl_value);
apfl_list
apfl_list_incref(apfl_list list)
{
if (list == NULL) {
return NULL;
}
list->refcount++;
return list;
}
size_t
apfl_list_len(apfl_list list)
apfl_list_len(struct list_header *list)
{
return list->len;
}
void
apfl_list_unref(struct apfl_allocator allocator, apfl_list list)
{
if (list == NULL || !apfl_refcount_dec(&list->refcount)) {
return;
}
for (size_t i = 0; i < list->len; i++) {
apfl_value_deinit(allocator, &list->items[i]);
}
FREE_LIST(allocator, list->items, list->cap);
FREE_OBJ(allocator, list);
}
static bool
dict_keys_eq(void *opaque, const void *a, const void *b)
{
@ -64,100 +26,51 @@ static apfl_hash
dict_calc_hash(void *opaque, const void *key)
{
(void)opaque;
return value_hash(*(const struct apfl_value *)key);
}
static void
dict_destroy_key_or_value(void *opaque, void *kv)
{
struct apfl_allocator *allocator = opaque;
apfl_value_deinit(*allocator, kv);
}
static void
dict_copy_key_or_value(void *opaque, void *_dest, void *_src)
{
(void)opaque;
struct apfl_value *dest = _dest;
struct apfl_value *src = _src;
*dest = apfl_value_incref(*src);
return apfl_value_hash(*(const struct apfl_value *)key);
}
static bool
dict_copy_opaque(void **_dst, void *src)
dict_init_hashmap(struct apfl_allocator allocator, struct apfl_hashmap *map)
{
struct apfl_allocator *allocator = src;
struct apfl_allocator *dst;
if ((dst = ALLOC_OBJ(*allocator, struct apfl_allocator)) == NULL) {
return false;
}
*dst = *allocator;
*_dst = dst;
return true;
}
static void
dict_deinit_opaque(void *opaque)
{
struct apfl_allocator *allocator = opaque;
FREE_OBJ(*allocator, allocator);
}
static bool
init_hashmap_for_dict(struct apfl_allocator allocator, struct apfl_hashmap *map)
{
struct apfl_allocator *allocator_ptr = ALLOC_OBJ(allocator, struct apfl_allocator);
if (allocator_ptr == NULL) {
return false;
}
*allocator_ptr = allocator;
bool ok = apfl_hashmap_init(
map,
allocator,
(struct apfl_hashmap_callbacks) {
.opaque = allocator_ptr,
.keys_eq = dict_keys_eq,
.calc_hash = dict_calc_hash,
.destroy_key = dict_destroy_key_or_value,
.destroy_value = dict_destroy_key_or_value,
.copy_key = dict_copy_key_or_value,
.copy_value = dict_copy_key_or_value,
.copy_opaque = dict_copy_opaque,
.deinit_opaque = dict_deinit_opaque,
},
sizeof(struct apfl_value),
sizeof(struct apfl_value)
);
if (!ok) {
FREE_OBJ(allocator, allocator_ptr);
}
return ok;
}
apfl_dict
apfl_dict_incref(apfl_dict dict)
struct dict_header *
apfl_dict_new(struct gc *gc)
{
if (dict == NULL) {
struct apfl_hashmap map;
if (!dict_init_hashmap(gc->allocator, &map)) {
return NULL;
}
dict->refcount++;
struct dict_header *dict = apfl_gc_new_dict(gc);
if (dict == NULL) {
apfl_hashmap_deinit(&map);
return NULL;
}
*dict = (struct dict_header) {
.map = map,
.copy_on_write = false,
};
return dict;
}
void
apfl_dict_unref(struct apfl_allocator allocator, apfl_dict dict)
apfl_dict_deinit(struct dict_header *header)
{
if (dict == NULL || !apfl_refcount_dec(&dict->refcount)) {
return;
}
apfl_hashmap_deinit(&dict->map);
FREE_OBJ(allocator, dict);
apfl_hashmap_deinit(&header->map);
}
static void
@ -177,7 +90,7 @@ print(unsigned indent, FILE *out, struct apfl_value value, bool skip_first_inden
apfl_print_indented(first_indent, out, "%f", value.number);
return;
case VALUE_STRING:
sv = apfl_string_view_from(value.string);
sv = apfl_string_view_from(*value.string);
apfl_print_indented(first_indent, out, "\"" APFL_STR_FMT "\"", APFL_STR_FMT_ARGS(sv));
return;
case VALUE_LIST:
@ -227,31 +140,6 @@ apfl_value_move(struct apfl_value *src)
return out;
}
struct apfl_value
apfl_value_incref(struct apfl_value value)
{
switch (value.type) {
case VALUE_NIL:
case VALUE_BOOLEAN:
case VALUE_NUMBER:
// Nothing to do
return value;
case VALUE_STRING:
value.string = apfl_refcounted_string_incref(value.string);
return value;
case VALUE_LIST:
value.list = apfl_list_incref(value.list);
return value;
case VALUE_DICT:
value.dict = apfl_dict_incref(value.dict);
return value;
}
assert(false);
return value;
}
void
apfl_value_print(struct apfl_value value, FILE *out)
{
@ -260,7 +148,7 @@ apfl_value_print(struct apfl_value value, FILE *out)
}
static bool
list_eq(apfl_list a, apfl_list b)
list_eq(struct list_header *a, struct list_header *b)
{
if (a == b) {
return true;
@ -325,7 +213,7 @@ apfl_value_eq(const struct apfl_value a, const struct apfl_value b)
case VALUE_NUMBER:
return a.number == b.number;
case VALUE_STRING:
return apfl_string_eq(a.string, b.string);
return a.string == b.string || apfl_string_eq(*a.string, *b.string);
case VALUE_LIST:
return list_eq(a.list, b.list);
case VALUE_DICT:
@ -336,294 +224,188 @@ apfl_value_eq(const struct apfl_value a, const struct apfl_value b)
return false;
}
struct apfl_editable_list_data {
struct apfl_allocator allocator;
struct apfl_value *items;
size_t len;
size_t cap;
};
apfl_editable_list
apfl_editable_list_new(struct apfl_allocator allocator)
struct list_header *
apfl_list_new(struct gc *gc, size_t initial_cap)
{
apfl_editable_list elist = ALLOC_OBJ(allocator, struct apfl_editable_list_data);
if (elist == NULL) {
return NULL;
}
elist->allocator = allocator;
elist->items = NULL;
elist->len = 0;
elist->cap = 0;
return elist;
}
static bool
editable_list_append_values(apfl_editable_list elist, struct apfl_value *values, size_t len)
{
if (!apfl_resizable_ensure_cap_for_more_elements(
elist->allocator,
sizeof(struct apfl_value),
(void **)&elist->items,
elist->len,
&elist->cap,
len
)) {
return false;
}
for (size_t i = 0; i < len; i++) {
elist->items[elist->len] = apfl_value_incref(values[i]);
elist->len++;
}
return true;
}
bool
apfl_editable_list_append(apfl_editable_list elist, struct apfl_value value)
{
bool ok = editable_list_append_values(elist, &value, 1);
apfl_value_deinit(elist->allocator, &value);
return ok;
}
bool
apfl_editable_list_append_list(apfl_editable_list elist, apfl_list list)
{
bool ok = editable_list_append_values(elist, list->items, list->len);
apfl_list_unref(elist->allocator, list);
return ok;
}
void
apfl_editable_list_destroy(apfl_editable_list elist)
{
if (elist == NULL) {
return;
}
if (elist->items != NULL) {
for (size_t i = 0; i < elist->len; i++) {
apfl_value_deinit(elist->allocator, &elist->items[i]);
}
FREE_LIST(elist->allocator, elist->items, elist->cap);
}
FREE_OBJ(elist->allocator, elist);
}
apfl_list
apfl_editable_list_finalize(apfl_editable_list elist)
{
apfl_list list = ALLOC_OBJ(elist->allocator, struct apfl_list_data);
if (list == NULL) {
apfl_editable_list_destroy(elist);
return NULL;
}
list->refcount = 1;
list->items = elist->items; // TODO: Maybe shrink memory with realloc?
list->len = elist->len;
list->cap = elist->cap;
FREE_OBJ(elist->allocator, elist);
return list;
}
struct apfl_editable_dict_data {
struct apfl_allocator allocator;
struct apfl_hashmap map;
};
apfl_editable_dict
apfl_editable_dict_new(struct apfl_allocator allocator)
{
apfl_editable_dict ed = ALLOC_OBJ(allocator, struct apfl_editable_dict_data);
if (ed == NULL) {
return NULL;
}
ed->allocator = allocator;
if (!init_hashmap_for_dict(allocator, &ed->map)) {
FREE_OBJ(allocator, ed);
return NULL;
}
return ed;
}
apfl_editable_dict
apfl_editable_dict_new_from_dict(struct apfl_allocator allocator, apfl_dict dict)
{
if (dict->refcount == 0) {
return NULL;
}
apfl_editable_dict ed = ALLOC_OBJ(allocator, struct apfl_editable_dict_data);
if (ed == NULL) {
apfl_dict_unref(allocator, dict);
return NULL;
}
ed->allocator = allocator;
if (dict->refcount == 1) {
// We're the only one having a reference to the dict. We can directly use it's hashmap!
ed->map = apfl_hashmap_move(&dict->map);
} else {
// There are other places referencing the dict, we need to create a shallow copy first.
if (!apfl_hashmap_copy(&ed->map, dict->map)) {
FREE_OBJ(allocator, ed);
apfl_dict_unref(allocator, dict);
struct apfl_value *items = NULL;
if (initial_cap > 0) {
items = ALLOC_LIST(gc->allocator, struct apfl_value, initial_cap);
if (items == NULL) {
return NULL;
}
}
apfl_dict_unref(allocator, dict);
return ed;
}
bool
apfl_editable_dict_get(
apfl_editable_dict ed,
struct apfl_value key,
struct apfl_value *out
) {
bool ok = apfl_hashmap_get(&ed->map, &key, out);
apfl_value_deinit(ed->allocator, &key);
return ok;
}
bool
apfl_editable_dict_set(apfl_editable_dict ed, struct apfl_value key, struct apfl_value value)
{
if (ed == NULL) {
return false;
struct list_header *list = apfl_gc_new_list(gc);
if (list == NULL) {
FREE_LIST(gc->allocator, items, initial_cap);
return NULL;
}
bool ok = apfl_hashmap_set(&ed->map, &key, &value);
apfl_value_deinit(ed->allocator, &key);
apfl_value_deinit(ed->allocator, &value);
return ok;
*list = (struct list_header) {
.items = items,
.len = 0,
.cap = initial_cap,
.copy_on_write = false,
};
return list;
}
void
apfl_editable_dict_delete(apfl_editable_dict ed, struct apfl_value key)
apfl_list_deinit(struct apfl_allocator allocator, struct list_header *list)
{
if (ed == NULL) {
return;
}
apfl_hashmap_delete(&ed->map, &key);
apfl_value_deinit(ed->allocator, &key);
FREE_LIST(allocator, list->items, list->cap);
}
apfl_dict
apfl_editable_dict_finalize(apfl_editable_dict ed)
bool
apfl_list_splice(
struct gc *gc,
struct list_header **dst_ptr,
size_t cut_start,
size_t cut_len,
const struct apfl_value *other,
size_t other_len
) {
struct list_header *dst = *dst_ptr;
if (!apfl_resizable_check_cut_args(dst->len, cut_start, cut_len)) {
return false;
}
if (!dst->copy_on_write) {
return apfl_resizable_splice(
gc->allocator,
sizeof(struct apfl_value),
(void **)&dst->items,
&dst->len,
&dst->cap,
cut_start,
cut_len,
other,
other_len
);
}
size_t len = dst->len - cut_len + other_len;
struct apfl_value *items = ALLOC_LIST(gc->allocator, struct apfl_value, len);
if (len > 0 && items == NULL) {
return false;
}
struct list_header *new_list = apfl_gc_new_list(gc);
if (new_list == NULL) {
FREE_LIST(gc->allocator, items, len);
return false;
}
// Note that we set the COW flag here, as the values now live in two places.
for (size_t i = 0; i < cut_start; i++) {
items[i] = apfl_value_set_cow_flag(dst->items[i]);
}
for (size_t i = 0; i < other_len; i++) {
items[cut_start + i] = apfl_value_set_cow_flag(other[i]);
}
for (size_t i = cut_start + cut_len; i < dst->len; i++) {
items[other_len + i - cut_len] = apfl_value_set_cow_flag(dst->items[i]);
}
*new_list = (struct list_header) {
.items = items,
.len = len,
.cap = len,
.copy_on_write = false,
};
*dst_ptr = new_list;
return true;
}
/* Returns a dictionary for editing. Will create a copy, if neccessary.
* *_dict must be known to the garbage collector!
*/
static struct dict_header *
dict_get_for_editing(struct gc *gc, struct dict_header **_dict)
{
if (ed == NULL) {
struct dict_header *dict = *_dict;
if (!dict->copy_on_write) {
return dict;
}
struct dict_header copy = {.copy_on_write = false};
if (!apfl_hashmap_copy(&copy.map, dict->map)) {
return NULL;
}
apfl_dict dict = ALLOC_OBJ(ed->allocator, struct apfl_dict_data);
// Set the COW flags of all keys and values in the copy.
HASHMAP_EACH(&copy.map, cur) {
struct apfl_value *item;
apfl_hashmap_cursor_peek_key(cur, (void **)&item);
*item = apfl_value_set_cow_flag(*item);
apfl_hashmap_cursor_peek_value(cur, (void **)&item);
*item = apfl_value_set_cow_flag(*item);
}
dict = apfl_gc_new_dict(gc);
if (dict == NULL) {
apfl_editable_dict_destroy(ed);
apfl_hashmap_deinit(&copy.map);
return NULL;
}
dict->refcount = 1;
dict->map = apfl_hashmap_move(&ed->map);
FREE_OBJ(ed->allocator, ed);
*dict = copy;
*_dict = dict;
return dict;
}
void
apfl_editable_dict_destroy(apfl_editable_dict ed)
{
if (ed == NULL) {
return;
}
apfl_hashmap_deinit(&ed->map);
FREE_OBJ(ed->allocator, ed);
}
void
apfl_value_deinit(struct apfl_allocator allocator, struct apfl_value *value)
{
switch (value->type) {
case VALUE_NIL:
case VALUE_BOOLEAN:
case VALUE_NUMBER:
goto ok;
case VALUE_STRING:
apfl_refcounted_string_unref(allocator, value->string);
value->string = NULL;
goto ok;
case VALUE_LIST:
apfl_list_unref(allocator, value->list);
value->list = NULL;
goto ok;
case VALUE_DICT:
apfl_dict_unref(allocator, value->dict);
value->dict = NULL;
goto ok;
}
assert(false);
ok:
value->type = VALUE_NIL;
}
/* Set a key-value pair in a raw dictionary.
* *_dict, k and v must all be known to the garbage collector!
*/
bool
apfl_list_get_item(struct apfl_allocator allocator, apfl_list list, size_t index, struct apfl_value *out)
{
if (index >= list->len) {
apfl_list_unref(allocator, list);
apfl_dict_set_raw(
struct gc *gc,
struct dict_header **_dict,
struct apfl_value k,
struct apfl_value v
) {
struct dict_header *dict = dict_get_for_editing(gc, _dict);
if (dict == NULL) {
return false;
}
*out = apfl_value_incref(list->items[index]);
apfl_list_unref(allocator, list);
return apfl_hashmap_set(&dict->map, &k, &v);
}
static bool
list_get_item(struct list_header *list, size_t index, struct apfl_value *out)
{
if (index >= list->len) {
return false;
}
*out = list->items[index];
return true;
}
bool
apfl_dict_get_item(struct apfl_allocator allocator, apfl_dict dict, struct apfl_value key, struct apfl_value *out)
{
bool ok = apfl_hashmap_get(&dict->map, &key, out);
apfl_dict_unref(allocator, dict);
apfl_value_deinit(allocator, &key);
return ok;
}
static enum get_item_result
get_item(struct apfl_allocator allocator, /*borrowed*/ struct apfl_value container, /*borrowed*/ struct apfl_value key, struct apfl_value *out)
value_get_item_inner(struct apfl_value container, struct apfl_value key, struct apfl_value *out)
{
if (container.type == VALUE_LIST) {
if (key.type != VALUE_NUMBER) {
return GET_ITEM_WRONG_KEY_TYPE;
}
return apfl_list_get_item(
allocator,
apfl_list_incref(container.list),
return list_get_item(
container.list,
(size_t)key.number,
out
)
? GET_ITEM_OK
: GET_ITEM_KEY_DOESNT_EXIST;
} else if (container.type == VALUE_DICT) {
return apfl_dict_get_item(
allocator,
apfl_dict_incref(container.dict),
apfl_value_incref(key),
return apfl_hashmap_get(
&container.dict->map,
&key,
out
)
? GET_ITEM_OK
@ -634,16 +416,34 @@ get_item(struct apfl_allocator allocator, /*borrowed*/ struct apfl_value contain
}
enum get_item_result
apfl_value_get_item(struct apfl_allocator allocator, struct apfl_value container, struct apfl_value key, struct apfl_value *out)
apfl_value_get_item(struct apfl_value container, struct apfl_value key, struct apfl_value *out)
{
enum get_item_result result = get_item(allocator, container, key, out);
apfl_value_deinit(allocator, &container);
apfl_value_deinit(allocator, &key);
enum get_item_result result = value_get_item_inner(container, key, out);
if (result == GET_ITEM_OK) {
*out = apfl_value_set_cow_flag(*out);
}
return result;
}
static apfl_hash
value_hash(const struct apfl_value value)
struct apfl_value
apfl_value_set_cow_flag(struct apfl_value value)
{
switch (value.type) {
case VALUE_LIST:
value.list->copy_on_write = true;
break;
case VALUE_DICT:
value.dict->copy_on_write = true;
break;
default:
break;
}
return value;
}
apfl_hash
apfl_value_hash(const struct apfl_value value)
{
apfl_hash hash = apfl_hash_fnv1a(&value.type, sizeof(enum value_type));
@ -659,12 +459,12 @@ value_hash(const struct apfl_value value)
hash = apfl_hash_fnv1a_add(&value.number, sizeof(apfl_number), hash);
goto ok;
case VALUE_STRING:
sv = apfl_string_view_from(value.string);
sv = apfl_string_view_from(*value.string);
hash = apfl_hash_fnv1a_add(sv.bytes, sv.len, hash);
goto ok;
case VALUE_LIST:
for (size_t i = 0; i < value.list->len; i++) {
apfl_hash item_hash = value_hash(value.list->items[i]);
apfl_hash item_hash = apfl_value_hash(value.list->items[i]);
hash = apfl_hash_fnv1a_add(&item_hash, sizeof(apfl_hash), hash);
}
goto ok;
@ -680,3 +480,56 @@ value_hash(const struct apfl_value value)
ok:
return hash;
}
struct gc_object *
apfl_value_get_gc_object(struct apfl_value value)
{
switch (value.type) {
case VALUE_NIL:
case VALUE_BOOLEAN:
case VALUE_NUMBER:
return NULL;
case VALUE_STRING:
return GC_OBJECT_FROM(value.string, GC_TYPE_STRING);
case VALUE_LIST:
return GC_OBJECT_FROM(value.list, GC_TYPE_LIST);
case VALUE_DICT:
return GC_OBJECT_FROM(value.dict, GC_TYPE_DICT);
}
assert(false);
return NULL;
}
static void
call_visitor_for_value(gc_visitor cb, void *opaque, struct apfl_value value)
{
struct gc_object *child = apfl_value_get_gc_object(value);
if (child != NULL) {
cb(opaque, child);
}
}
void
apfl_gc_list_traverse(struct list_header *list, gc_visitor cb, void *opaque)
{
for (size_t i = 0; i < list->len; i++) {
call_visitor_for_value(cb, opaque, list->items[i]);
}
}
void
apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque)
{
HASHMAP_EACH(&dict->map, cur) {
struct apfl_value *k;
struct apfl_value *v;
apfl_hashmap_cursor_peek_key(cur, (void **)&k);
apfl_hashmap_cursor_peek_value(cur, (void **)&v);
call_visitor_for_value(cb, opaque, *k);
call_visitor_for_value(cb, opaque, *v);
}
}

View file

@ -5,11 +5,16 @@
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include "apfl.h"
#include "gc.h"
#include "hashmap.h"
enum value_type {
VALUE_NIL,
VALUE_BOOLEAN,
@ -20,29 +25,39 @@ enum value_type {
// TODO: functions/closures
};
struct apfl_list_data;
typedef struct apfl_list_data *apfl_list;
struct list_header {
struct apfl_value *items;
size_t len;
size_t cap;
bool copy_on_write;
};
struct apfl_dict_data;
typedef struct apfl_dict_data *apfl_dict;
struct dict_header {
struct apfl_hashmap map;
bool copy_on_write;
};
typedef struct dict_header *apfl_dict;
struct apfl_value {
enum value_type type;
union {
bool boolean;
apfl_number number;
apfl_refcounted_string string;
apfl_list list;
apfl_dict dict;
struct apfl_string *string;
struct list_header *list;
struct dict_header *dict;
};
};
enum apfl_value_type apfl_value_type_to_abstract_type(enum value_type);
#define VALUE_IS_A(v, t) (apfl_value_type_to_abstract_type((v).type) == (t))
bool apfl_value_eq(const struct apfl_value, const struct apfl_value);
struct apfl_value apfl_value_move(struct apfl_value *src);
struct apfl_value apfl_value_incref(struct apfl_value);
void apfl_value_print(struct apfl_value, FILE *);
void apfl_value_deinit(struct apfl_allocator, struct apfl_value *);
apfl_hash apfl_value_hash(const struct apfl_value);
enum get_item_result {
GET_ITEM_OK,
@ -51,46 +66,40 @@ enum get_item_result {
GET_ITEM_WRONG_KEY_TYPE,
};
enum get_item_result apfl_value_get_item(struct apfl_allocator allocator, struct apfl_value container, struct apfl_value key, struct apfl_value *out);
enum get_item_result apfl_value_get_item(struct apfl_value container, struct apfl_value key, struct apfl_value *out);
apfl_list apfl_list_incref(apfl_list);
size_t apfl_list_len(apfl_list);
bool apfl_list_get_item(struct apfl_allocator, apfl_list, size_t index, struct apfl_value *out);
void apfl_list_unref(struct apfl_allocator, apfl_list);
// Set the Copy On Write flag of a value (if it's a value with such a flag).
// Returns the same value again.
struct apfl_value apfl_value_set_cow_flag(struct apfl_value);
apfl_dict apfl_dict_incref(apfl_dict);
bool apfl_dict_get_item(struct apfl_allocator, apfl_dict, struct apfl_value key, struct apfl_value *out);
void apfl_dict_unref(struct apfl_allocator, apfl_dict);
struct list_header *apfl_list_new(struct gc *, size_t initial_cap);
struct apfl_editable_list_data;
typedef struct apfl_editable_list_data *apfl_editable_list;
size_t apfl_list_len(struct list_header *);
void apfl_list_deinit(struct apfl_allocator, struct list_header *);
apfl_editable_list apfl_editable_list_new(struct apfl_allocator);
bool apfl_editable_list_append(apfl_editable_list, struct apfl_value);
bool apfl_editable_list_append_list(apfl_editable_list, apfl_list);
void apfl_editable_list_destroy(apfl_editable_list);
bool
apfl_list_splice(
struct gc *gc,
struct list_header **dst_ptr,
size_t cut_start,
size_t cut_len,
const struct apfl_value *other,
size_t other_len
);
/* Finalize the list and return a non-editable list.
* This also destroys the editable list object, so it's no longer safe to use it.
* Returns NULL on failure
*/
apfl_list apfl_editable_list_finalize(apfl_editable_list);
struct dict_header *apfl_dict_new(struct gc *);
bool apfl_dict_set_raw(
struct gc *gc,
struct dict_header **_dict,
struct apfl_value k,
struct apfl_value v
);
void apfl_dict_deinit(struct dict_header *);
struct apfl_editable_dict_data;
typedef struct apfl_editable_dict_data *apfl_editable_dict;
apfl_editable_dict apfl_editable_dict_new(struct apfl_allocator);
apfl_editable_dict apfl_editable_dict_new_from_dict(struct apfl_allocator, apfl_dict);
bool apfl_editable_dict_get(apfl_editable_dict, struct apfl_value key, struct apfl_value *out);
bool apfl_editable_dict_set(apfl_editable_dict, struct apfl_value key, struct apfl_value value);
void apfl_editable_dict_delete(apfl_editable_dict, struct apfl_value key);
void apfl_editable_dict_destroy(apfl_editable_dict);
/* Finalize the dictionary and return a non-editable dictionary.
* This also destroys the editable dictionary object, so it's no longer safe to use it.
* Returns NULL on failure
*/
apfl_dict apfl_editable_dict_finalize(apfl_editable_dict);
// Functions for garbage collection
struct gc_object *apfl_value_get_gc_object(struct apfl_value);
void apfl_gc_list_traverse(struct list_header *, gc_visitor, void *);
void apfl_gc_dict_traverse(struct dict_header *, gc_visitor, void *);
#ifdef __cplusplus
}