apfl/src/apfl.h

594 lines
19 KiB
C
Raw Normal View History

2021-12-10 20:22:16 +00:00
#ifndef APFL_H
#define APFL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
// Allocator
// apfl_allocator_cb is called to (re)allocate and free memory.
//
// If called with newsize = 0, the memory of oldsize bytes pointed to by
// oldptr should be freed. If the NULL-pointer is freed, nothing should
// happen. A freeing must return the NULL-pointer and is not allowed to
// fail.
//
// If oldptr = NULL and oldsize = 0, memory of size newsize should be
// allocated and the pointer to it returned. If the allocation fails, NULL
// should be returned.
//
// Reallocation is done by calling with the old pointer, old size and new
// size. On success the new pointer should be returned and the old pointer
// (unless they're the same) no longer used. On failure, NULL should be
// returned and the oldptr should stay valid.
typedef void * (*apfl_allocator_cb) (void *opaque, void *oldptr, size_t oldsize, size_t newsize);
struct apfl_allocator {
void *opaque;
apfl_allocator_cb alloc;
};
struct apfl_allocator apfl_allocator_default(void);
struct apfl_allocator apfl_allocator_wrap_verifying(struct apfl_allocator *wrapped);
2021-12-10 20:22:16 +00:00
typedef double apfl_number;
struct apfl_position {
int line;
int col;
};
bool apfl_position_eq(struct apfl_position, struct apfl_position);
// Strings
struct apfl_string_view {
const char *bytes;
size_t len;
};
struct apfl_string {
char *bytes;
size_t len;
// TODO: Not sure, if it's a good idea to expose this. We now need the actual
2022-02-25 20:38:14 +00:00
// size of the underlying allocation though. Maybe we should better
// shrink the cap to len wen we're done building a string?
size_t cap;
2021-12-10 20:22:16 +00:00
};
#define APFL_STR_FMT "%.*s"
#define APFL_STR_FMT_ARGS(s) (int)(s).len,(s).bytes
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);
#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, \
char *: apfl_string_view_from_cstr, \
const char *: apfl_string_view_from_const_cstr \
2021-12-10 20:22:16 +00:00
)(s)
int apfl_string_view_cmp(struct apfl_string_view, struct apfl_string_view);
#define apfl_string_cmp(a, b) apfl_string_view_cmp(apfl_string_view_from(a), apfl_string_view_from(b))
#define apfl_string_eq(a, b) (apfl_string_cmp((a), (b)) == 0)
2021-12-10 20:22:16 +00:00
struct apfl_string apfl_string_blank(void);
void apfl_string_deinit(struct apfl_allocator allocator, struct apfl_string *);
2021-12-10 20:22:16 +00:00
struct apfl_string apfl_string_move(struct apfl_string *src);
/**
* Copies a string from src to dst. dst must point to a blank string.
* Returns true on success, false otherwise (if the necessary memory could not
* be allocated).
*/
bool apfl_string_copy(struct apfl_allocator allocator, struct apfl_string *dst, struct apfl_string_view src);
2021-12-10 20:22:16 +00:00
struct apfl_string_builder {
struct apfl_allocator allocator;
2021-12-10 20:22:16 +00:00
char *bytes;
size_t len;
size_t cap;
};
void apfl_string_builder_init(struct apfl_allocator allocator, struct apfl_string_builder *);
2021-12-10 20:22:16 +00:00
void apfl_string_builder_deinit(struct apfl_string_builder *);
bool apfl_string_builder_append(struct apfl_string_builder *, struct apfl_string_view);
bool apfl_string_builder_append_byte(struct apfl_string_builder *, char byte);
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))))
// Tokens
enum apfl_token_type {
APFL_TOK_LPAREN,
APFL_TOK_RPAREN,
APFL_TOK_LBRACKET,
APFL_TOK_RBRACKET,
APFL_TOK_LBRACE,
APFL_TOK_RBRACE,
APFL_TOK_MAPSTO,
APFL_TOK_EXPAND,
APFL_TOK_DOT,
APFL_TOK_AT,
APFL_TOK_SEMICOLON,
APFL_TOK_LINEBREAK,
APFL_TOK_CONTINUE_LINE,
APFL_TOK_COMMENT,
APFL_TOK_COMMA,
APFL_TOK_QUESTION_MARK,
APFL_TOK_STRINGIFY,
APFL_TOK_ASSIGN,
APFL_TOK_LOCAL_ASSIGN,
APFL_TOK_NUMBER,
APFL_TOK_NAME,
APFL_TOK_STRING,
};
struct apfl_token {
enum apfl_token_type type;
struct apfl_position position;
union {
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
struct apfl_string text;
2021-12-10 20:22:16 +00:00
apfl_number number;
};
};
void apfl_token_deinit(struct apfl_allocator allocator, struct apfl_token *);
2021-12-10 20:22:16 +00:00
const char *apfl_token_type_name(enum apfl_token_type);
void apfl_token_print(struct apfl_token, FILE *);
// Errors
enum apfl_error_type {
APFL_ERR_MALLOC_FAILED,
APFL_ERR_INPUT_ERROR,
APFL_ERR_UNEXPECTED_EOF,
APFL_ERR_EXPECTED_EQ_AFTER_COLON,
APFL_ERR_UNEXPECTED_BYTE,
2021-12-10 20:22:16 +00:00
APFL_ERR_UNEXPECTED_BYTE_IN_NUMBER,
APFL_ERR_EXPECTED_DIGIT,
APFL_ERR_EXPECTED_HEX_IN_HEX_ESCAPE,
APFL_ERR_INVALID_ESCAPE_SEQUENCE,
APFL_ERR_NO_LINEBREAK_AFTER_CONTINUE_LINE,
APFL_ERR_UNEXPECTED_TOKEN,
APFL_ERR_MISMATCHING_CLOSING_BRACKET,
APFL_ERR_UNEXPECTED_EOF_AFTER_TOKEN,
APFL_ERR_STATEMENTS_BEFORE_PARAMETERS,
APFL_ERR_EMPTY_ASSIGNMENT_BEFORE_PARAMETERS,
APFL_ERR_UNEXPECTED_EXPRESSION,
APFL_ERR_INVALID_ASSIGNMENT_LHS,
APFL_ERR_EMPTY_ASSIGNMENT,
APFL_ERR_ONLY_ONE_EXPAND_ALLOWED,
APFL_ERR_UNEXPECTED_CONSTANT_IN_MEMBER_ACCESS,
APFL_ERR_UNEXPECTED_EXPR_IN_MEMBER_ACCESS,
APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS,
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
APFL_ERR_NOT_IMPLEMENTED,
2021-12-10 20:22:16 +00:00
};
2021-12-18 23:27:34 +00:00
const char *apfl_error_type_name(enum apfl_error_type);
2021-12-10 20:22:16 +00:00
struct apfl_error {
enum apfl_error_type type;
// Optional data
struct apfl_position position;
struct apfl_position position2;
enum apfl_token_type token_type;
enum apfl_token_type token_type2;
char byte;
};
void apfl_error_print(struct apfl_error, FILE *);
struct apfl_error apfl_error_simple(enum apfl_error_type);
bool apfl_error_is_fatal_type(enum apfl_error_type);
#define APFL_ERROR_IS_FATAL(err) (apfl_error_is_fatal_type((err).type))
enum apfl_expr_type {
APFL_EXPR_LIST,
APFL_EXPR_DICT,
APFL_EXPR_CALL,
APFL_EXPR_SIMPLE_FUNC,
APFL_EXPR_COMPLEX_FUNC,
APFL_EXPR_ASSIGNMENT,
APFL_EXPR_DOT,
APFL_EXPR_AT,
APFL_EXPR_CONSTANT,
APFL_EXPR_VAR,
APFL_EXPR_BLANK,
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_list_item {
struct apfl_expr *expr;
bool expand;
};
struct apfl_expr_list {
struct apfl_expr_list_item *items;
size_t len;
};
struct apfl_expr_dict_pair {
struct apfl_expr *k;
struct apfl_expr *v;
};
struct apfl_expr_dict {
struct apfl_expr_dict_pair *items;
size_t len;
size_t cap;
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_call {
struct apfl_expr *callee;
struct apfl_expr_list arguments;
};
struct apfl_expr_body {
struct apfl_expr *items;
size_t len;
size_t cap;
2021-12-10 20:22:16 +00:00
};
enum apfl_expr_const_type {
APFL_EXPR_CONST_NIL,
APFL_EXPR_CONST_BOOLEAN,
APFL_EXPR_CONST_STRING,
APFL_EXPR_CONST_NUMBER,
};
struct apfl_expr_const {
enum apfl_expr_const_type type;
union {
// variant nil is without data
bool boolean;
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
struct apfl_string string;
2021-12-10 20:22:16 +00:00
apfl_number number;
};
};
struct apfl_expr_param_predicate {
struct apfl_expr_param *lhs;
struct apfl_expr *rhs;
};
enum apfl_expr_param_type {
APFL_EXPR_PARAM_VAR,
APFL_EXPR_PARAM_CONSTANT,
APFL_EXPR_PARAM_PREDICATE,
APFL_EXPR_PARAM_LIST,
APFL_EXPR_PARAM_BLANK,
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_params {
struct apfl_expr_params_item *params;
2021-12-10 20:22:16 +00:00
size_t len;
size_t cap;
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_param {
enum apfl_expr_param_type type;
union {
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
struct apfl_string var;
2021-12-10 20:22:16 +00:00
struct apfl_expr_const constant;
struct apfl_expr_param_predicate predicate;
struct apfl_expr_params list;
};
};
struct apfl_expr_params_item {
bool expand;
struct apfl_expr_param param;
};
2021-12-10 20:22:16 +00:00
struct apfl_expr_subfunc {
struct apfl_expr_params params;
2022-04-11 20:44:04 +00:00
struct apfl_expr_body body;
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_complex_func {
struct apfl_expr_subfunc *subfuncs;
size_t len;
size_t cap;
2021-12-10 20:22:16 +00:00
};
enum apfl_expr_assignable_type {
APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER,
2021-12-10 20:22:16 +00:00
APFL_EXPR_ASSIGNABLE_CONSTANT,
APFL_EXPR_ASSIGNABLE_PREDICATE,
APFL_EXPR_ASSIGNABLE_LIST,
APFL_EXPR_ASSIGNABLE_BLANK,
2021-12-10 20:22:16 +00:00
};
enum apfl_expr_assignable_var_or_member_type {
APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR,
APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT,
APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT,
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_assignable_var_or_member_dot {
struct apfl_expr_assignable_var_or_member *lhs;
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
struct apfl_string rhs;
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_assignable_var_or_member_at {
struct apfl_expr_assignable_var_or_member *lhs;
struct apfl_expr *rhs;
};
struct apfl_expr_assignable_var_or_member {
enum apfl_expr_assignable_var_or_member_type type;
union {
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
struct apfl_string var;
struct apfl_expr_assignable_var_or_member_dot dot;
struct apfl_expr_assignable_var_or_member_at at;
};
};
struct apfl_expr_assignable_predicate {
2021-12-10 20:22:16 +00:00
struct apfl_expr_assignable *lhs;
struct apfl_expr *rhs;
};
struct apfl_expr_assignable_list {
struct apfl_expr_assignable_list_item *items;
2021-12-10 20:22:16 +00:00
size_t len;
};
struct apfl_expr_assignable {
enum apfl_expr_assignable_type type;
union {
struct apfl_expr_assignable_var_or_member var_or_member;
2021-12-10 20:22:16 +00:00
struct apfl_expr_const constant;
struct apfl_expr_assignable_predicate predicate;
struct apfl_expr_assignable_list list;
};
};
struct apfl_expr_assignable_list_item {
struct apfl_expr_assignable assignable;
bool expand;
};
2021-12-10 20:22:16 +00:00
struct apfl_expr_assignment {
bool local;
struct apfl_expr_assignable lhs;
struct apfl_expr *rhs;
};
struct apfl_expr_dot {
struct apfl_expr *lhs;
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
struct apfl_string rhs;
2021-12-10 20:22:16 +00:00
};
struct apfl_expr_at {
struct apfl_expr *lhs;
struct apfl_expr *rhs;
};
struct apfl_expr {
enum apfl_expr_type type;
union {
struct apfl_expr_list list;
struct apfl_expr_dict dict;
struct apfl_expr_call call;
2022-04-11 20:44:04 +00:00
struct apfl_expr_body simple_func;
2021-12-10 20:22:16 +00:00
struct apfl_expr_complex_func complex_func;
struct apfl_expr_assignment assignment;
struct apfl_expr_dot dot;
struct apfl_expr_at at;
struct apfl_expr_const constant;
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
struct apfl_string var;
// blank has no further data
2021-12-10 20:22:16 +00:00
};
struct apfl_position position;
};
void apfl_expr_print(struct apfl_expr, FILE *);
bool apfl_expr_eq(struct apfl_expr, struct apfl_expr);
// Begin deinit functions
void apfl_expr_deinit(struct apfl_allocator allocator, struct apfl_expr *);
void apfl_expr_list_deinit(struct apfl_allocator allocator, struct apfl_expr_list *);
void apfl_expr_list_item_deinit(struct apfl_allocator allocator, struct apfl_expr_list_item *);
void apfl_expr_dict_pair_deinit(struct apfl_allocator allocator, struct apfl_expr_dict_pair *);
void apfl_expr_dict_deinit(struct apfl_allocator allocator, struct apfl_expr_dict *);
void apfl_expr_call_deinit(struct apfl_allocator allocator, struct apfl_expr_call *);
2022-04-11 20:44:04 +00:00
void apfl_expr_body_deinit(struct apfl_allocator allocator, struct apfl_expr_body *);
void apfl_expr_const_deinit(struct apfl_allocator allocator, struct apfl_expr_const *);
void apfl_expr_param_predicate_deinit(struct apfl_allocator allocator, struct apfl_expr_param_predicate *);
void apfl_expr_params_deinit(struct apfl_allocator allocator, struct apfl_expr_params *);
void apfl_expr_params_item_deinit(struct apfl_allocator allocator, struct apfl_expr_params_item *);
void apfl_expr_param_deinit(struct apfl_allocator allocator, struct apfl_expr_param *);
void apfl_expr_subfunc_deinit(struct apfl_allocator allocator, struct apfl_expr_subfunc *);
void apfl_expr_complex_func_deinit(struct apfl_allocator allocator, struct apfl_expr_complex_func *);
void apfl_expr_assignable_predicate_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_predicate *);
void apfl_expr_assignable_list_item_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_list_item *);
void apfl_expr_assignable_list_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_list *);
void apfl_expr_assignable_var_or_member_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member_dot *);
void apfl_expr_assignable_var_or_member_at_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member_at *);
void apfl_expr_assignable_var_or_member_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member *);
void apfl_expr_assignable_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable *);
void apfl_expr_assignment_deinit(struct apfl_allocator allocator, struct apfl_expr_assignment *);
void apfl_expr_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_dot *);
void apfl_expr_at_deinit(struct apfl_allocator allocator, struct apfl_expr_at *);
2021-12-10 20:22:16 +00:00
// End deinit functions
// Begin move functions
struct apfl_expr apfl_expr_move(struct apfl_expr *);
struct apfl_expr_list apfl_expr_list_move(struct apfl_expr_list *);
struct apfl_expr_list_item apfl_expr_list_item_move(struct apfl_expr_list_item *);
struct apfl_expr_dict_pair apfl_expr_dict_pair_move(struct apfl_expr_dict_pair *);
struct apfl_expr_dict apfl_expr_dict_move(struct apfl_expr_dict *);
struct apfl_expr_call apfl_expr_call_move(struct apfl_expr_call *);
2022-04-11 20:44:04 +00:00
struct apfl_expr_body apfl_expr_body_move(struct apfl_expr_body *);
struct apfl_expr_const apfl_expr_const_move(struct apfl_expr_const *);
struct apfl_expr_param_predicate apfl_expr_param_predicate_move(struct apfl_expr_param_predicate *);
struct apfl_expr_params apfl_expr_params_move(struct apfl_expr_params *);
struct apfl_expr_param apfl_expr_param_move(struct apfl_expr_param *);
struct apfl_expr_subfunc apfl_expr_subfunc_move(struct apfl_expr_subfunc *);
struct apfl_expr_complex_func apfl_expr_complex_func_move(struct apfl_expr_complex_func *);
struct apfl_expr_assignable_var_or_member apfl_expr_assignable_var_or_member_move(struct apfl_expr_assignable_var_or_member *);
struct apfl_expr_assignable_predicate apfl_expr_assignable_predicate_move(struct apfl_expr_assignable_predicate *);
struct apfl_expr_assignable_dot apfl_expr_assignable_dot_move(struct apfl_expr_assignable_dot *);
struct apfl_expr_assignable_at apfl_expr_assignable_at_move(struct apfl_expr_assignable_at *);
struct apfl_expr_assignable_list apfl_expr_assignable_list_move(struct apfl_expr_assignable_list *);
struct apfl_expr_assignable apfl_expr_assignable_move(struct apfl_expr_assignable *);
struct apfl_expr_assignment apfl_expr_assignment_move(struct apfl_expr_assignment *);
struct apfl_expr_dot apfl_expr_dot_move(struct apfl_expr_dot *);
struct apfl_expr_at apfl_expr_at_move(struct apfl_expr_at *);
2021-12-10 20:22:16 +00:00
// End move functions
enum apfl_parse_result {
APFL_PARSE_OK,
APFL_PARSE_EOF,
APFL_PARSE_ERROR,
};
struct apfl_tokenizer;
typedef struct apfl_tokenizer *apfl_tokenizer_ptr;
typedef bool (*apfl_source_reader_cb)(void *context, char *buf, size_t *len, bool need);
apfl_tokenizer_ptr apfl_tokenizer_new(struct apfl_allocator allocator, apfl_source_reader_cb, void *context);
2021-12-10 20:22:16 +00:00
void apfl_tokenizer_destroy(apfl_tokenizer_ptr);
enum apfl_parse_result apfl_tokenizer_next(apfl_tokenizer_ptr, bool need);
/* Get the current token.
* Return value is undefined when the last call to apfl_tokenizer_next did not
* return APFL_PARSE_OK.
*/
struct apfl_token apfl_tokenizer_get_token(apfl_tokenizer_ptr);
/* Get the current error.
* Return value is undefined when the last call to apfl_tokenizer_next did not
* return APFL_PARSE_ERROR.
*/
struct apfl_error apfl_tokenizer_get_error(apfl_tokenizer_ptr);
/* An apfl_source_reader_cb implementation to have a string view as a source.
* Use together with apfl_string_source_reader_new.
*/
bool apfl_string_source_reader(void *, char *, size_t *len, bool);
void *apfl_string_source_reader_new(struct apfl_allocator allocator, struct apfl_string_view);
void apfl_string_source_reader_destroy(void *);
2021-12-10 20:22:16 +00:00
struct apfl_parser_token_source {
enum apfl_parse_result (*next)(void *, bool need);
struct apfl_token (*get_token)(void *);
struct apfl_error (*get_error)(void *);
void *opaque;
};
struct apfl_parser_token_source apfl_tokenizer_as_token_source(apfl_tokenizer_ptr);
2021-12-10 20:22:16 +00:00
struct apfl_parser;
typedef struct apfl_parser *apfl_parser_ptr;
apfl_parser_ptr apfl_parser_new(struct apfl_allocator allocator, struct apfl_parser_token_source);
2021-12-10 20:22:16 +00:00
/* Destroys the parser.
* Note that if the token source needs it's own destruction, you'll have to do
* that yourself after destroying the parser.
*/
void apfl_parser_destroy(apfl_parser_ptr);
enum apfl_parse_result apfl_parser_next(apfl_parser_ptr);
/* Get the current error.
* Return value is undefined when the last call to apfl_parser_next did not
* return APFL_PARSE_ERROR.
*/
struct apfl_error apfl_parser_get_error(apfl_parser_ptr);
/* Get the current expression.
* Return value is undefined when the last call to apfl_parser_next did not
* return APFL_PARSE_OK.
*/
struct apfl_expr apfl_parser_get_expr(apfl_parser_ptr);
struct apfl_ctx_data;
typedef struct apfl_ctx_data *apfl_ctx;
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
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 {
APFL_RESULT_OK, // Evaluation succeeded, value is on the stack
// TODO: No further details are yet provided on (fatal) error. Not quite
// sure what to return / how errors/exceptions should work. Maybe a
// value + a backtrace?
APFL_RESULT_ERR,
APFL_RESULT_ERR_FATAL,
};
apfl_ctx apfl_ctx_new(struct apfl_allocator);
void apfl_ctx_destroy(apfl_ctx);
enum apfl_result apfl_eval(apfl_ctx, struct apfl_expr);
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
// 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 *);
2021-12-10 20:22:16 +00:00
#ifdef __cplusplus
}
#endif
#endif