Implement matching assignments

This allows the destructuring of lists into individual values.
We can have arbitrarily nested lists, can check for constant values and can
have up to one '~'-prefixed variable per list, that will capture the
remaining elements of the list.

It is implemented as a second set of bytecode instructions, which define a
matcher. These matchers should also enable us to implement the same pattern
matching capabiities for function parameters.

Not all matching features are implemented yet, predicate matching and
matching into a dictionary key is not implemented yet.
This commit is contained in:
Laria 2022-07-28 20:46:32 +02:00
parent e61caea2ff
commit 1630314dc7
13 changed files with 1192 additions and 47 deletions

View file

@ -14,6 +14,7 @@ libapfl_a_SOURCES += format.c
libapfl_a_SOURCES += gc.c
libapfl_a_SOURCES += hashmap.c
libapfl_a_SOURCES += globals.c
libapfl_a_SOURCES += matcher.c
libapfl_a_SOURCES += messages.c
libapfl_a_SOURCES += parser.c
libapfl_a_SOURCES += position.c
@ -34,6 +35,7 @@ apfl_internal_headers += format.h
apfl_internal_headers += gc.h
apfl_internal_headers += globals.h
apfl_internal_headers += hashmap.h
apfl_internal_headers += matcher.h
apfl_internal_headers += resizable.h
apfl_internal_headers += scope.h
apfl_internal_headers += strings.h

View file

@ -720,6 +720,8 @@ struct apfl_messages {
const char *not_a_function;
const char *wrong_type;
const char *io_error;
const char *value_doesnt_match;
const char *invalid_matcher_state;
};
extern const struct apfl_messages apfl_messages;

View file

@ -54,11 +54,14 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi
case INSN_NEXT_LINE:
case INSN_DROP:
case INSN_CALL:
case INSN_MATCHER_MUST_MATCH:
case INSN_MATCHER_DROP:
break;
case INSN_NUMBER:
case INSN_LIST:
case INSN_SET_LINE:
case INSN_GET_BY_INDEX_KEEP:
case INSN_MATCHER_SET_VAL:
i++;
break;
case INSN_STRING:
@ -75,6 +78,16 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.body, GC_TYPE_INSTRUCTIONS));
break;
case INSN_MATCHER_LOAD:
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.matcher, GC_TYPE_MATCHER_INSTRUCTIONS));
break;
case INSN_VAR_SET_FROM_MATCHER:
case INSN_VAR_SET_LOCAL_FROM_MATCHER:
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING));
i++;
break;
}
}
}
@ -129,7 +142,67 @@ apfl_instruction_to_string(enum instruction insn)
return "INSN_CALL";
case INSN_FUNC:
return "INSN_FUNC";
case INSN_MATCHER_LOAD:
return "INSN_MATCHER_LOAD";
case INSN_MATCHER_SET_VAL:
return "INSN_MATCHER_SET_VAL";
case INSN_MATCHER_MUST_MATCH:
return "INSN_MATCHER_MUST_MATCH";
case INSN_MATCHER_DROP:
return "INSN_MATCHER_DROP";
case INSN_VAR_SET_FROM_MATCHER:
return "INSN_VAR_SET_FROM_MATCHER";
case INSN_VAR_SET_LOCAL_FROM_MATCHER:
return "INSN_VAR_SET_LOCAL_FROM_MATCHER";
}
return "??";
}
const char *
apfl_matcher_instruction_to_string(enum matcher_instruction insn)
{
switch (insn) {
case MATCHER_IGNORE:
return "MATCHER_IGNORE";
case MATCHER_CAPTURE:
return "MATCHER_CAPTURE";
case MATCHER_CHECK_CONST:
return "MATCHER_CHECK_CONST";
case MATCHER_CHECK_PRED:
return "MATCHER_CHECK_PRED";
case MATCHER_ENTER_LIST:
return "MATCHER_ENTER_LIST";
case MATCHER_LEAVE_LIST:
return "MATCHER_LEAVE_LIST";
case MATCHER_CONTINUE_FROM_END:
return "MATCHER_CONTINUE_FROM_END";
case MATCHER_REMAINDING:
return "MATCHER_REMAINDING";
}
return "??";
}
struct matcher_instruction_list *
apfl_matcher_instructions_new(struct gc *gc)
{
struct matcher_instruction_list *milist = apfl_gc_new_matcher_instructions(gc);
if (milist == NULL) {
return NULL;
}
*milist = (struct matcher_instruction_list) {
.instructions = NULL,
.len = 0,
.cap = 0,
.capture_count = 0,
.value_count = 0,
};
return milist;
}
void
apfl_matcher_instructions_deinit(struct apfl_allocator allocator, struct matcher_instruction_list *milist)
{
FREE_LIST(allocator, milist->instructions, milist->cap);
}

View file

@ -9,30 +9,60 @@ extern "C" {
#include "gc.h"
enum matcher_instruction {
MATCHER_IGNORE,
MATCHER_CAPTURE, // with index as capture index
MATCHER_CHECK_CONST, // with index as values index
MATCHER_CHECK_PRED, // with index as values index
MATCHER_ENTER_LIST,
MATCHER_LEAVE_LIST,
MATCHER_CONTINUE_FROM_END,
MATCHER_REMAINDING,
};
union matcher_instruction_or_arg {
enum matcher_instruction instruction;
size_t index;
};
struct matcher_instruction_list {
union matcher_instruction_or_arg *instructions;
size_t len;
size_t cap;
size_t capture_count;
size_t value_count;
};
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_GET_BY_INDEX_KEEP, // ( list/dict -- list/dict value ), arg: index
INSN_VAR_GET, // ( -- value ), arg: string
INSN_VAR_SET, // ( value -- value ), arg: string
INSN_VAR_SET_LOCAL, // ( value -- value ), arg: string
INSN_VAR_NEW, // ( -- ), arg: string
INSN_VAR_NEW_LOCAL, // ( -- ), arg: string
INSN_MOVE_TO_LOCAL_VAR, // ( value -- ), arg: string
INSN_NEXT_LINE, // ( -- )
INSN_SET_LINE, // ( -- ), arg: count (new line number)
INSN_DROP, // ( value -- )
INSN_CALL, // ( func list -- value )
INSN_FUNC, // ( -- func ), arg: body
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_GET_BY_INDEX_KEEP, // ( list/dict -- list/dict value ), arg: index
INSN_VAR_GET, // ( -- value ), arg: string
INSN_VAR_SET, // ( value -- value ), arg: string
INSN_VAR_SET_LOCAL, // ( value -- value ), arg: string
INSN_VAR_NEW, // ( -- ), arg: string
INSN_VAR_NEW_LOCAL, // ( -- ), arg: string
INSN_MOVE_TO_LOCAL_VAR, // ( value -- ), arg: string
INSN_NEXT_LINE, // ( -- )
INSN_SET_LINE, // ( -- ), arg: count (new line number)
INSN_DROP, // ( value -- )
INSN_CALL, // ( func list -- value )
INSN_FUNC, // ( -- func ), arg: body
INSN_MATCHER_LOAD, // ( -- ), arg: matcher
INSN_MATCHER_SET_VAL, // ( val -- ), arg: index
INSN_MATCHER_MUST_MATCH, // ( val -- )
INSN_MATCHER_DROP, // ( -- )
INSN_VAR_SET_FROM_MATCHER, // ( -- ), args: string, index
INSN_VAR_SET_LOCAL_FROM_MATCHER, // ( -- ), args: string, index
};
union instruction_or_arg {
@ -42,6 +72,7 @@ union instruction_or_arg {
size_t count;
size_t index;
struct instruction_list *body;
struct matcher_instruction_list *matcher;
};
struct instruction_list {
@ -53,12 +84,16 @@ struct instruction_list {
};
const char *apfl_instruction_to_string(enum instruction);
const char *apfl_matcher_instruction_to_string(enum matcher_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 *);
struct matcher_instruction_list *apfl_matcher_instructions_new(struct gc *);
void apfl_matcher_instructions_deinit(struct apfl_allocator, struct matcher_instruction_list *);
#ifdef __cplusplus
}
#endif

View file

@ -103,6 +103,53 @@ ilist_put_index(struct instruction_list *ilist, size_t index)
ILIST_PUT(ilist, index, index, "put_index: %d", (int)index)
}
static void
ilist_put_matcher(struct instruction_list *ilist, struct matcher_instruction_list *milist)
{
ILIST_PUT(ilist, matcher, milist, "put_matcher: %p", (void *)milist)
}
static bool
milist_ensure_cap(struct compiler *compiler, struct matcher_instruction_list *milist, size_t n)
{
return malloc_failure_on_false(compiler, apfl_resizable_ensure_cap_for_more_elements(
compiler->gc->allocator,
sizeof(union matcher_instruction_or_arg),
(void **)&milist->instructions,
milist->len,
&milist->cap,
n
));
}
static void
milist_put_insn(struct matcher_instruction_list *milist, enum matcher_instruction instruction)
{
assert(milist->cap > 0);
assert(milist->len < milist->cap);
milist->instructions[milist->len] = (union matcher_instruction_or_arg) {
.instruction = instruction,
};
milist->len++;
DEBUG_LOG("milist %p: put_insn: %s\n", (void *)milist, apfl_matcher_instruction_to_string(instruction));
}
static void
milist_put_index(struct matcher_instruction_list *milist, size_t index)
{
assert(milist->cap > 0);
assert(milist->len < milist->cap);
milist->instructions[milist->len] = (union matcher_instruction_or_arg) {
.index = index,
};
milist->len++;
DEBUG_LOG("milist %p: put_index: %d\n", (void *)milist, (int)index);
}
static bool
string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in)
{
@ -270,10 +317,276 @@ tmp_ilist(struct compiler *compiler, int line)
return ilist;
}
struct compile_assignable_ilists {
struct instruction_list *prelude;
struct instruction_list *newvars;
struct instruction_list *setvars;
};
static bool
compile_assignable(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_assignable *assignable,
struct matcher_instruction_list *milist,
struct compile_assignable_ilists ilists
);
static bool
compile_assignable_var_or_member(
struct compiler *compiler,
bool local,
struct apfl_expr_assignable_var_or_member *var_or_member,
struct matcher_instruction_list *milist,
struct compile_assignable_ilists ilists
) {
size_t index = milist->capture_count++;
if (var_or_member->type != APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR) {
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); // TODO: Implement me
return false;
}
/* TODO: What should happen, if we try to match the same variable twice?
*
* Right now, the second capture will overwrite the first one, but it would
* be pretty sweet if this would actually compare the value with the first
* capture, you could write == in apfl itself then:
*
* == := {
* x x -> true
* x y -> false
* }
*/
TRY(milist_ensure_cap(compiler, milist, 2));
milist_put_insn(milist, MATCHER_CAPTURE);
milist_put_index(milist, index);
TRY(ilist_ensure_cap(compiler, ilists.newvars, 2));
struct apfl_string *varname = NULL;
TRY(string_move_into_new(compiler, &varname, &var_or_member->var));
ilist_put_insn(ilists.newvars, local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW);
ilist_put_string(ilists.newvars, varname);
TRY(ilist_ensure_cap(compiler, ilists.setvars, 3));
ilist_put_insn(ilists.setvars, local ? INSN_VAR_SET_LOCAL_FROM_MATCHER : INSN_VAR_SET_FROM_MATCHER);
ilist_put_string(ilists.setvars, varname);
ilist_put_index(ilists.setvars, index);
return true;
}
static bool
compile_assignable_constant(
struct compiler *compiler,
struct apfl_expr_const *constant,
struct matcher_instruction_list *milist,
struct compile_assignable_ilists ilists
) {
size_t index = milist->value_count++;
TRY(compile_constant(compiler, constant, ilists.prelude));
TRY(ilist_ensure_cap(compiler, ilists.prelude, 2));
ilist_put_insn(ilists.prelude, INSN_MATCHER_SET_VAL);
ilist_put_index(ilists.prelude, index);
TRY(milist_ensure_cap(compiler, milist, 3));
milist_put_insn(milist, MATCHER_CHECK_CONST);
milist_put_index(milist, index);
milist_put_insn(milist, MATCHER_IGNORE);
return true;
}
static bool
compile_assignable_predicate(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_assignable_predicate *predicate,
struct matcher_instruction_list *milist,
struct compile_assignable_ilists ilists
) {
size_t index = milist->value_count++;
TRY(compile_expr(compiler, predicate->rhs, ilists.prelude));
TRY(ilist_ensure_cap(compiler, ilists.prelude, 2));
ilist_put_insn(ilists.prelude, INSN_MATCHER_SET_VAL);
ilist_put_index(ilists.prelude, index);
TRY(milist_ensure_cap(compiler, milist, 2));
milist_put_insn(milist, MATCHER_CHECK_PRED);
milist_put_index(milist, index);
return compile_assignable(compiler, local, position, predicate->lhs, milist, ilists);
}
static bool
compile_assignable_list_expand(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_assignable_list *list,
struct matcher_instruction_list *milist,
struct compile_assignable_ilists ilists,
size_t expand_at
) {
assert(expand_at < list->len);
struct apfl_expr_assignable_list_item *expand_item = &list->items[expand_at];
assert(expand_item->expand);
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_CONTINUE_FROM_END);
for (size_t i = list->len; i-- > expand_at+1; ) {
struct apfl_expr_assignable_list_item *item = &list->items[i];
if (item->expand) {
compiler->error = (struct apfl_error) {
.type = APFL_ERR_ONLY_ONE_EXPAND_ALLOWED,
.position = position,
};
return false;
}
TRY(compile_assignable(compiler, local, position, &item->assignable, milist, ilists));
}
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_REMAINDING);
TRY(compile_assignable(compiler, local, position, &expand_item->assignable, milist, ilists));
return true;
}
static bool
compile_assignable_list(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_assignable_list *list,
struct matcher_instruction_list *milist,
struct compile_assignable_ilists ilists
) {
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_ENTER_LIST);
for (size_t i = 0; i < list->len; i++) {
struct apfl_expr_assignable_list_item *item = &list->items[i];
if (item->expand) {
return compile_assignable_list_expand(
compiler,
local,
position,
list,
milist,
ilists,
i
);
}
TRY(compile_assignable(compiler, local, position, &item->assignable, milist, ilists));
}
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_LEAVE_LIST);
return true;
}
static bool
compile_assignable(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_assignable *assignable,
struct matcher_instruction_list *milist,
struct compile_assignable_ilists ilists
) {
switch (assignable->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
return compile_assignable_var_or_member(compiler, local, &assignable->var_or_member, milist, ilists);
case APFL_EXPR_ASSIGNABLE_CONSTANT:
return compile_assignable_constant(compiler, &assignable->constant, milist, ilists);
case APFL_EXPR_ASSIGNABLE_PREDICATE:
return compile_assignable_predicate(compiler, local, position, &assignable->predicate, milist, ilists);
case APFL_EXPR_ASSIGNABLE_LIST:
return compile_assignable_list(compiler, local, position, &assignable->list, milist, ilists);
case APFL_EXPR_ASSIGNABLE_BLANK:
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_IGNORE);
return true;
}
assert(false);
return false;
}
static bool
concat_ilists(struct compiler *compiler, struct instruction_list *out, const struct instruction_list *in)
{
TRY(ilist_ensure_cap(compiler, out, in->len));
memcpy(
out->instructions + out->len,
in->instructions,
sizeof(union instruction_or_arg) * in->len
);
out->len += in->len;
return true;
}
static bool
compile_complex_assignment(
struct compiler *compiler,
struct apfl_expr_assignment *assignment,
struct apfl_position position,
struct instruction_list *ilist
) {
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct matcher_instruction_list *milist;
if ((milist = apfl_matcher_instructions_new(compiler->gc)) == NULL) {
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
return false;
}
ilist_put_insn(ilist, INSN_MATCHER_LOAD);
ilist_put_matcher(ilist, milist);
struct compile_assignable_ilists ilists = {
.prelude = ilist,
};
MALLOC_FAIL_IF_NULL(compiler, (ilists.newvars = tmp_ilist(compiler, position.line)));
MALLOC_FAIL_IF_NULL(compiler, (ilists.setvars = tmp_ilist(compiler, position.line)));
TRY(compile_assignable(compiler, assignment->local, position, &assignment->lhs, milist, ilists));
TRY(concat_ilists(compiler, ilist, ilists.newvars));
TRY(compile_expr(compiler, assignment->rhs, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_MATCHER_MUST_MATCH);
TRY(concat_ilists(compiler, ilist, ilists.setvars));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_MATCHER_DROP);
return true;
}
static bool
compile_assignment(
struct compiler *compiler,
struct apfl_expr_assignment *assignment,
struct apfl_position position,
struct instruction_list *ilist
) {
if (
@ -290,7 +603,12 @@ compile_assignment(
);
}
// TODO: Implement other assignables
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
bool ok = compile_complex_assignment(compiler, assignment, position, ilist);
apfl_gc_tmproots_restore(compiler->gc, tmproots);
return ok;
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
return false;
}
@ -449,7 +767,7 @@ compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instructi
case APFL_EXPR_VAR:
return compile_var(compiler, &expr->var, ilist);
case APFL_EXPR_ASSIGNMENT:
return compile_assignment(compiler, &expr->assignment, ilist);
return compile_assignment(compiler, &expr->assignment, expr->position, ilist);
}
assert(false);

View file

@ -466,6 +466,12 @@ gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, vo
GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC)
);
break;
case CSE_MATCHER:
visitor(
opaque,
GC_OBJECT_FROM(cse.matcher.matcher, GC_TYPE_MATCHER)
);
break;
}
}
@ -511,6 +517,15 @@ void
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
{
deinit_stack(allocator, &entry->stack);
switch (entry->type) {
case CSE_FUNCTION:
case CSE_CFUNCTION:
break;
case CSE_MATCHER:
FREE_LIST(allocator, entry->matcher.matcher_stack, entry->matcher.matcher_stack_cap);
break;
}
}
struct call_stack_entry *

View file

@ -11,6 +11,7 @@ extern "C" {
#include "bytecode.h"
#include "hashmap.h"
#include "gc.h"
#include "matcher.h"
#include "value.h"
#include "scope.h"
@ -25,6 +26,7 @@ struct stack {
enum call_stack_entry_type {
CSE_FUNCTION,
CSE_CFUNCTION,
CSE_MATCHER,
};
struct func_call_stack_entry {
@ -36,12 +38,40 @@ struct func_call_stack_entry {
struct scope *closure_scope;
int execution_line;
struct matcher *matcher;
bool returning_from_matcher;
};
struct cfunc_call_stack_entry {
struct cfunction *func;
};
enum matcher_mode {
MATCHER_MODE_VALUE,
MATCHER_MODE_STOP,
MATCHER_MODE_LIST_START,
MATCHER_MODE_LIST_END,
MATCHER_MODE_LIST_REMAINING,
MATCHER_MODE_LIST_UNDERFLOW,
};
struct matcher_stack_entry {
enum matcher_mode mode;
size_t lower;
size_t upper;
};
struct matcher_call_stack_entry {
size_t pc;
struct matcher *matcher;
struct matcher_stack_entry *matcher_stack;
size_t matcher_stack_len;
size_t matcher_stack_cap;
};
struct call_stack_entry {
enum call_stack_entry_type type;
@ -50,6 +80,7 @@ struct call_stack_entry {
union {
struct func_call_stack_entry func;
struct cfunc_call_stack_entry cfunc;
struct matcher_call_stack_entry matcher;
};
};

View file

@ -8,11 +8,14 @@
#include "context.h"
#include "format.h"
#include "hashmap.h"
#include "matcher.h"
#include "resizable.h"
#include "strings.h"
#include "value.h"
static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse);
static void evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse);
static void matcher_init_matching(apfl_ctx ctx, struct matcher *matcher);
static void
stack_must_drop(apfl_ctx ctx, apfl_stackidx index)
@ -20,23 +23,41 @@ stack_must_drop(apfl_ctx ctx, apfl_stackidx index)
assert(apfl_stack_drop(ctx, index));
}
#define ABSTRACT_GET_ARGUMENT(i, ilist, arg) \
if (*i >= ilist->len) { \
return false; \
} \
\
*arg = ilist->instructions[(*i)++]; \
return true;
#define ABSTRACT_MUST_GET_ARG(get, ctx, i, ilist, arg) \
if (!get(i, ilist, arg)) { \
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); \
}
static bool
get_argument(size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg)
{
if (*i >= ilist->len) {
return false;
}
*arg = ilist->instructions[(*i)++];
return true;
ABSTRACT_GET_ARGUMENT(i, ilist, arg)
}
static void
must_get_argument(apfl_ctx ctx, size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg)
{
if (!get_argument(i, ilist, arg)) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
ABSTRACT_MUST_GET_ARG(get_argument, ctx, i, ilist, arg)
}
static bool
get_matcher_argument(size_t *i, struct matcher_instruction_list *milist, union matcher_instruction_or_arg *arg)
{
ABSTRACT_GET_ARGUMENT(i, milist, arg)
}
static void
must_get_matcher_argument(apfl_ctx ctx, size_t *i, struct matcher_instruction_list *milist, union matcher_instruction_or_arg *arg)
{
ABSTRACT_MUST_GET_ARG(get_matcher_argument, ctx, i, milist, arg)
}
static struct func_call_stack_entry *
@ -149,10 +170,8 @@ try_variable_update_existing_for_scope_type(
}
static void
variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local)
variable_set_value(apfl_ctx ctx, struct apfl_string *name, bool local, struct apfl_value value)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
bool was_set = false;
if (!local) {
was_set = try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_LOCAL)
@ -167,6 +186,14 @@ variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool lo
apfl_raise_alloc_error(ctx);
}
}
}
static void
variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
variable_set_value(ctx, name, local, value);
if (keep_on_stack) {
// If the value should be kept on the stack, the value is now in two
@ -261,14 +288,8 @@ call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
}
static void
return_from_function_inner(apfl_ctx ctx)
call_stack_drop(apfl_ctx ctx)
{
struct apfl_value value;
if (!apfl_stack_pop(ctx, &value, -1)) {
// No return value on the stack. Return nil instead
value = (struct apfl_value) { .type = VALUE_NIL };
}
assert(ctx->call_stack.len > 0);
apfl_call_stack_entry_deinit(ctx->gc.allocator, apfl_call_stack_cur_entry(ctx));
@ -284,6 +305,18 @@ return_from_function_inner(apfl_ctx ctx)
ctx->call_stack.len - 1
)
);
}
static void
return_from_function_inner(apfl_ctx ctx)
{
struct apfl_value value;
if (!apfl_stack_pop(ctx, &value, -1)) {
// No return value on the stack. Return nil instead
value = (struct apfl_value) { .type = VALUE_NIL };
}
call_stack_drop(ctx);
apfl_stack_must_push(ctx, value);
}
@ -309,7 +342,7 @@ prepare_call(apfl_ctx ctx, size_t tmproots, struct apfl_value args, struct call_
}
// Keep evaluate instructions until we've returned from the current call stack.
// Must only be called with a CSE_FUNCTION on top of the call stack.
// Must not be called with a CSE_CFUNCTION on top of the call stack.
static void
evaluate_until_call_stack_return(apfl_ctx ctx)
{
@ -321,9 +354,18 @@ evaluate_until_call_stack_return(apfl_ctx ctx)
while (call_stack->len >= depth_started) {
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
assert(cse->type == CSE_FUNCTION);
evaluate(ctx, &cse->func);
switch (cse->type) {
case CSE_CFUNCTION:
assert(false);
break;
case CSE_FUNCTION:
evaluate(ctx, &cse->func);
break;
case CSE_MATCHER:
evaluate_matcher(ctx, &cse->matcher);
break;
}
}
}
@ -368,6 +410,8 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid
.scope = NULL,
.closure_scope = func.func->scope,
.execution_line = func.func->body->line,
.returning_from_matcher = false,
.matcher = NULL,
},
});
@ -410,10 +454,80 @@ apfl_call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index)
call(ctx, func_index, args_index, false);
}
static void
matcher_load(apfl_ctx ctx, struct func_call_stack_entry *cse, struct matcher_instruction_list *milist)
{
assert(cse != NULL);
if ((cse->matcher = apfl_matcher_new(&ctx->gc, milist)) == NULL) {
apfl_raise_alloc_error(ctx);
}
}
static void
matcher_set_val(apfl_ctx ctx, struct matcher *matcher, size_t index)
{
if (matcher == NULL) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
if (index >= matcher->instructions->value_count) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
matcher->values[index] = apfl_stack_must_pop(ctx, -1);
}
static void
variable_set_from_matcher_inner(
apfl_ctx ctx,
struct matcher *matcher,
struct apfl_string *varname,
size_t index,
bool local
) {
if (matcher == NULL) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
if (index >= matcher->instructions->capture_count) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
struct apfl_value value = apfl_value_move(&matcher->captures[index]);
must_tmproot_add_value(ctx, value);
variable_set_value(ctx, varname, local, value);
}
static void
variable_set_from_matcher(
apfl_ctx ctx,
struct matcher *matcher,
struct apfl_string *varname,
size_t index,
bool local
) {
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
variable_set_from_matcher_inner(ctx, matcher, varname, index, local);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
if (cse->returning_from_matcher) {
assert(cse->matcher != NULL);
if (!cse->matcher->result) {
cse->matcher = NULL;
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_doesnt_match);
}
cse->returning_from_matcher = false;
}
union instruction_or_arg arg;
union instruction_or_arg arg2;
size_t *pc = &cse->pc;
struct instruction_list *ilist = cse->instructions;
@ -510,6 +624,36 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
must_get_argument(ctx, pc, ilist, &arg);
func(ctx, arg.body);
goto continue_loop;
case INSN_MATCHER_LOAD:
must_get_argument(ctx, pc, ilist, &arg);
matcher_load(ctx, cse, arg.matcher);
goto continue_loop;
case INSN_MATCHER_SET_VAL:
must_get_argument(ctx, pc, ilist, &arg);
matcher_set_val(ctx, cse->matcher, arg.index);
goto continue_loop;
case INSN_MATCHER_MUST_MATCH:
// matcher_init_matching pushes a new call stack entry for the matcher onto the stack. We rturn from this
// So this new CSE gets executed. By setting returning_from_matcher, we know that we came from the matcher,
// once it returns.
matcher_init_matching(ctx, cse->matcher);
cse->returning_from_matcher = true;
return;
case INSN_MATCHER_DROP:
cse->matcher = NULL;
goto continue_loop;
case INSN_VAR_SET_FROM_MATCHER:
must_get_argument(ctx, pc, ilist, &arg);
must_get_argument(ctx, pc, ilist, &arg2);
variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, false);
goto continue_loop;
case INSN_VAR_SET_LOCAL_FROM_MATCHER:
must_get_argument(ctx, pc, ilist, &arg);
must_get_argument(ctx, pc, ilist, &arg2);
variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, true);
goto continue_loop;
}
assert(false);
@ -520,6 +664,388 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
return_from_function(ctx);
}
static void
matcher_stack_push(apfl_ctx ctx, struct matcher_call_stack_entry *cse, struct matcher_stack_entry entry)
{
if (!apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct matcher_stack_entry),
(void **)&cse->matcher_stack,
&cse->matcher_stack_len,
&cse->matcher_stack_cap,
&entry,
1
)) {
apfl_raise_alloc_error(ctx);
}
}
APFL_NORETURN static void
raise_invalid_matcher_state(apfl_ctx ctx)
{
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_matcher_state);
}
static void
matcher_stack_drop(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
if (cse->matcher_stack_len == 0) {
raise_invalid_matcher_state(ctx);
}
assert(
// We're shrinking, should not fail
apfl_resizable_resize(
ctx->gc.allocator,
sizeof(struct matcher_stack_entry),
(void **)&cse->matcher_stack,
&cse->matcher_stack_len,
&cse->matcher_stack_cap,
cse->matcher_stack_len-1
)
);
}
static void
matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
must_tmproot_add_value(ctx, value);
struct matcher_call_stack_entry matcher_cse = {
.pc = 0,
.matcher = matcher,
.matcher_stack = NULL,
.matcher_stack_len = 0,
.matcher_stack_cap = 0,
};
matcher_stack_push(ctx, &matcher_cse, (struct matcher_stack_entry) {
.mode = MATCHER_MODE_VALUE,
});
call_stack_push(ctx, (struct call_stack_entry) {
.type = CSE_MATCHER,
.stack = apfl_stack_new(),
.matcher = matcher_cse,
});
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value));
}
static void
matcher_init_matching(apfl_ctx ctx, struct matcher *matcher)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
matcher_init_matching_inner(ctx, matcher);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
matcher_check_index(apfl_ctx ctx, size_t count, size_t index)
{
if (index >= count) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
}
static struct matcher_stack_entry *
matcher_cur_stack_entry(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
if (cse->matcher_stack_len == 0) {
raise_invalid_matcher_state(ctx);
}
return &cse->matcher_stack[cse->matcher_stack_len-1];
}
static bool
matcher_current_val_in_mstack(apfl_ctx ctx, struct matcher_stack_entry *mstack, struct apfl_value *value)
{
struct apfl_value cur;
switch (mstack->mode) {
case MATCHER_MODE_VALUE:
case MATCHER_MODE_LIST_REMAINING:
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
*value = cur;
return true;
case MATCHER_MODE_STOP:
case MATCHER_MODE_LIST_UNDERFLOW:
return false;
case MATCHER_MODE_LIST_START:
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
if (cur.type != VALUE_LIST) {
raise_invalid_matcher_state(ctx);
}
if (mstack->lower >= cur.list->len) {
return false;
}
*value = cur.list->items[mstack->lower];
return true;
case MATCHER_MODE_LIST_END:
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
if (cur.type != VALUE_LIST) {
raise_invalid_matcher_state(ctx);
}
if (mstack->upper == 0) {
return NULL;
}
*value = cur.list->items[mstack->upper-1];
return true;
}
raise_invalid_matcher_state(ctx);
}
static bool
matcher_current_val(apfl_ctx ctx, struct matcher_call_stack_entry *cse, struct apfl_value *value)
{
struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse);
return matcher_current_val_in_mstack(ctx, mstack, value);
}
static bool
matcher_next(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
again:;
struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse);
switch (mstack->mode) {
case MATCHER_MODE_VALUE:
mstack->mode = MATCHER_MODE_STOP;
if (!apfl_stack_drop(ctx, -1)) {
raise_invalid_matcher_state(ctx);
}
return true;
case MATCHER_MODE_STOP:
case MATCHER_MODE_LIST_UNDERFLOW:
raise_invalid_matcher_state(ctx);
return false;
case MATCHER_MODE_LIST_START:
mstack->lower++;
return true;
case MATCHER_MODE_LIST_END:
if (mstack->upper <= mstack->lower) {
mstack->mode = MATCHER_MODE_LIST_UNDERFLOW;
return false;
}
mstack->upper--;
return true;
case MATCHER_MODE_LIST_REMAINING:
if (!apfl_stack_drop(ctx, -1)) {
raise_invalid_matcher_state(ctx);
}
matcher_stack_drop(ctx, cse);
goto again; // We also need to advance the previous stack entry,
// like we would do when doing a MATCHER_LEAVE_LIST
}
raise_invalid_matcher_state(ctx);
}
static bool
matcher_enter_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse);
struct apfl_value cur;
if (!matcher_current_val_in_mstack(ctx, mstack, &cur)) {
return false;
}
if (cur.type != VALUE_LIST) {
return false;
}
size_t len = cur.list->len;
apfl_stack_must_push(ctx, cur);
matcher_stack_push(ctx, cse, (struct matcher_stack_entry) {
.mode = MATCHER_MODE_LIST_START,
.lower = 0,
.upper = len,
});
return true;
}
static void
matcher_continue_from_end(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse);
if (mstack->mode != MATCHER_MODE_LIST_START) {
raise_invalid_matcher_state(ctx);
}
mstack->mode = MATCHER_MODE_LIST_END;
}
static void
matcher_remainding(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse);
struct apfl_value cur;
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
if (
(mstack->mode != MATCHER_MODE_LIST_START && mstack->mode != MATCHER_MODE_LIST_END)
|| cur.type != VALUE_LIST
) {
raise_invalid_matcher_state(ctx);
}
if (mstack->lower > mstack->upper) {
raise_invalid_matcher_state(ctx);
}
struct list_header *cur_list = cur.list;
assert(cur_list->len >= mstack->upper);
size_t len = mstack->upper - mstack->lower;
apfl_list_create(ctx, len);
struct apfl_value new_val = apfl_stack_must_get(ctx, -1);
assert(new_val.type == VALUE_LIST);
struct list_header *new_list = new_val.list;
assert(new_list->cap == len);
assert(new_list->len == 0);
for (size_t i = mstack->lower; i < mstack->upper; i++) {
new_list->items[new_list->len++] = cur_list->items[i];
}
assert(new_list->len == len);
if (!apfl_stack_drop(ctx, -2)) { // Drop the original list
raise_invalid_matcher_state(ctx);
}
mstack->mode = MATCHER_MODE_LIST_REMAINING;
}
static bool
matcher_leave_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse);
if (mstack->mode != MATCHER_MODE_LIST_START) {
raise_invalid_matcher_state(ctx);
}
if (mstack->lower < mstack->upper) {
// List was not completely matched
return false;
}
if (!apfl_stack_drop(ctx, -1)) {
raise_invalid_matcher_state(ctx);
}
matcher_stack_drop(ctx, cse);
return matcher_next(ctx, cse);
}
static void
return_from_matcher(apfl_ctx ctx, bool result)
{
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
assert(cse->type == CSE_MATCHER);
cse->matcher.matcher->result = result;
call_stack_drop(ctx);
cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
assert(cse->type == CSE_FUNCTION);
}
#define RETURN_WITHOUT_MATCH(ctx) \
do { \
return_from_matcher((ctx), false); \
return; \
} while (0)
#define RETURN_WITHOUT_MATCH_ON_FALSE(ctx, x) \
do { \
if (!(x)) { \
RETURN_WITHOUT_MATCH(ctx); \
} \
} while (0)
static void
evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
union matcher_instruction_or_arg arg;
size_t *pc = &cse->pc;
struct matcher *matcher = cse->matcher;
struct matcher_instruction_list *milist = matcher->instructions;
while (*pc < milist->len) {
struct apfl_value cur;
switch (milist->instructions[(*pc)++].instruction) {
case MATCHER_CAPTURE:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
must_get_matcher_argument(ctx, pc, milist, &arg);
matcher_check_index(ctx, milist->capture_count, arg.index);
matcher->captures[arg.index] = apfl_value_set_cow_flag(cur);
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_next(ctx, cse));
goto continue_loop;
case MATCHER_IGNORE:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_next(ctx, cse));
goto continue_loop;
case MATCHER_CHECK_CONST:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
must_get_matcher_argument(ctx, pc, milist, &arg);
matcher_check_index(ctx, milist->value_count, arg.index);
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, apfl_value_eq(matcher->values[arg.index], cur));
goto continue_loop;
case MATCHER_CHECK_PRED:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
must_get_matcher_argument(ctx, pc, milist, &arg);
matcher_check_index(ctx, milist->value_count, arg.index);
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.feature_not_implemented); // TODO: Implement this
goto continue_loop;
case MATCHER_ENTER_LIST:
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_enter_list(ctx, cse));
goto continue_loop;
case MATCHER_LEAVE_LIST:
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_leave_list(ctx, cse));
goto continue_loop;
case MATCHER_CONTINUE_FROM_END:
matcher_continue_from_end(ctx, cse);
goto continue_loop;
case MATCHER_REMAINDING:
matcher_remainding(ctx, cse);
goto continue_loop;
}
assert(false);
continue_loop:;
}
return_from_matcher(
ctx,
// We've successfully matched everything, if there's only one stack element left and we're in stop mode
cse->matcher_stack_len == 1 && cse->matcher_stack[0].mode == MATCHER_MODE_STOP
);
}
struct apfl_iterative_runner_data {
apfl_ctx ctx;
apfl_tokenizer_ptr tokenizer;
@ -558,6 +1084,8 @@ iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr
.scope = runner->scope,
.closure_scope = NULL,
.execution_line = ilist->line,
.returning_from_matcher = false,
.matcher = NULL,
},
});
evaluate_until_call_stack_return(ctx);

View file

@ -6,6 +6,7 @@
#include "bytecode.h"
#include "context.h"
#include "gc.h"
#include "matcher.h"
#include "resizable.h"
#include "scope.h"
#include "value.h"
@ -29,6 +30,8 @@ struct gc_object {
struct stack stack;
struct function function;
struct cfunction cfunction;
struct matcher_instruction_list matcher_instructions;
struct matcher matcher;
};
enum gc_type type;
enum gc_status status;
@ -172,6 +175,8 @@ IMPL_NEW(struct instruction_list, apfl_gc_new_instructions, GC_T
IMPL_NEW(struct scope, apfl_gc_new_scope, GC_TYPE_SCOPE, scope )
IMPL_NEW(struct function, apfl_gc_new_func, GC_TYPE_FUNC, function )
IMPL_NEW(struct cfunction, apfl_gc_new_cfunc, GC_TYPE_CFUNC, cfunction )
IMPL_NEW(struct matcher_instruction_list, apfl_gc_new_matcher_instructions, GC_TYPE_MATCHER_INSTRUCTIONS, matcher_instructions)
IMPL_NEW(struct matcher, apfl_gc_new_matcher, GC_TYPE_MATCHER, matcher )
size_t
apfl_gc_tmproots_begin(struct gc *gc)
@ -271,6 +276,12 @@ visit_children(struct gc_object *object, gc_visitor cb, void *opaque)
case GC_TYPE_CFUNC:
apfl_gc_cfunc_traverse(&object->cfunction, cb, opaque);
return;
case GC_TYPE_MATCHER_INSTRUCTIONS:
// Intentionally left blank. Object doesn't reference other objects.
return;
case GC_TYPE_MATCHER:
apfl_gc_matcher_traverse(&object->matcher, cb, opaque);
return;
}
assert(false);
@ -339,6 +350,12 @@ deinit_object(struct gc *gc, struct gc_object *object)
case GC_TYPE_CFUNC:
apfl_cfunction_deinit(gc->allocator, &object->cfunction);
return;
case GC_TYPE_MATCHER_INSTRUCTIONS:
apfl_matcher_instructions_deinit(gc->allocator, &object->matcher_instructions);
return;
case GC_TYPE_MATCHER:
apfl_matcher_deinit(gc->allocator, &object->matcher);
return;
}
assert(false);
@ -427,6 +444,14 @@ apfl_gc_full(struct gc *gc)
gc->is_collecting = false;
}
void
apfl_gc_add_child(struct gc_object *parent, struct gc_object* child)
{
if (parent->status == GC_STATUS_BLACK) {
color_object_grey(child);
}
}
static const char *
dump_graph_bgcolor(enum gc_status status)
{
@ -471,6 +496,10 @@ type_to_string(enum gc_type type)
return "func";
case GC_TYPE_CFUNC:
return "cfunc";
case GC_TYPE_MATCHER_INSTRUCTIONS:
return "matcher instructions";
case GC_TYPE_MATCHER:
return "matcher";
}
assert(false);

View file

@ -29,6 +29,8 @@ enum gc_type {
GC_TYPE_SCOPE,
GC_TYPE_FUNC,
GC_TYPE_CFUNC,
GC_TYPE_MATCHER_INSTRUCTIONS,
GC_TYPE_MATCHER,
};
struct gc_tmproots {
@ -82,6 +84,8 @@ struct instruction_list* apfl_gc_new_instructions(struct gc *);
struct scope* apfl_gc_new_scope(struct gc *);
struct function* apfl_gc_new_func(struct gc *);
struct cfunction* apfl_gc_new_cfunc(struct gc *);
struct matcher_instruction_list* apfl_gc_new_matcher_instructions(struct gc *);
struct matcher* apfl_gc_new_matcher(struct gc *);
#ifdef __cplusplus
}

79
src/matcher.c Normal file
View file

@ -0,0 +1,79 @@
#include "alloc.h"
#include "gc.h"
#include "matcher.h"
static bool
init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len)
{
if (len == 0) {
return true;
}
if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) {
return false;
}
for (size_t i = 0; i < len; i++) {
(*list)[i] = (struct apfl_value) { .type = VALUE_NIL };
}
return true;
}
struct matcher *
apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist)
{
struct matcher matcher = {
.instructions = milist,
.values = NULL,
.captures = NULL,
.result = false,
};
if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) {
goto error;
}
if (!init_values_list(gc->allocator, &matcher.captures, milist->capture_count)) {
goto error;
}
struct matcher *gc_matcher = apfl_gc_new_matcher(gc);
if (gc_matcher == NULL) {
goto error;
}
*gc_matcher = matcher;
return gc_matcher;
error:
apfl_matcher_deinit(gc->allocator, &matcher);
return NULL;
}
void
apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher)
{
FREE_LIST(allocator, matcher->values, matcher->instructions->value_count);
FREE_LIST(allocator, matcher->captures, matcher->instructions->capture_count);
}
void
apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque)
{
visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS));
for (size_t i = 0; i < matcher->instructions->value_count; i++) {
struct gc_object *object = apfl_value_get_gc_object(matcher->values[i]);
if (object != NULL) {
visitor(opaque, object);
}
}
for (size_t i = 0; i < matcher->instructions->capture_count; i++) {
struct gc_object *object = apfl_value_get_gc_object(matcher->captures[i]);
if (object != NULL) {
visitor(opaque, object);
}
}
}

27
src/matcher.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef APFL_MATCHER_H
#define APFL_MATCHER_H
#ifdef __cplusplus
extern "C" {
#endif
#include "bytecode.h"
#include "gc.h"
#include "value.h"
struct matcher {
struct matcher_instruction_list *instructions;
struct apfl_value *values;
struct apfl_value *captures;
bool result;
};
struct matcher *apfl_matcher_new(struct gc *, struct matcher_instruction_list *);
void apfl_matcher_deinit(struct apfl_allocator, struct matcher *);
void apfl_gc_matcher_traverse(struct matcher *, gc_visitor, void *);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -18,4 +18,6 @@ const struct apfl_messages apfl_messages = {
.not_a_function = "Not a function",
.wrong_type = "Wrong type",
.io_error = "I/O error",
.value_doesnt_match = "Value does not match",
.invalid_matcher_state = "Matcher is in invalid state",
};