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:
parent
4088fdd1c3
commit
90a80152e1
24 changed files with 2331 additions and 1998 deletions
|
|
@ -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
|
||||
|
|
|
|||
66
src/apfl.h
66
src/apfl.h
|
|
@ -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
111
src/bytecode.c
Normal 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
57
src/bytecode.h
Normal 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
349
src/compile.c
Normal 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
19
src/compile.h
Normal 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
550
src/context.c
Normal 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
57
src/context.h
Normal 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
|
||||
|
|
@ -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
1455
src/eval.c
File diff suppressed because it is too large
Load diff
30
src/expr.c
30
src/expr.c
|
|
@ -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, ¶m->var);
|
||||
break;
|
||||
case APFL_EXPR_PARAM_CONSTANT:
|
||||
apfl_expr_const_deinit(allocator, ¶m->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
545
src/gc.c
Normal 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
86
src/gc.h
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
30
src/parser.c
30
src/parser.c
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
627
src/value.c
627
src/value.c
|
|
@ -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(©.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(©.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(©.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
95
src/value.h
95
src/value.h
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue