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:
parent
e61caea2ff
commit
1630314dc7
13 changed files with 1192 additions and 47 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
322
src/compile.c
322
src/compile.c
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 *
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
572
src/eval.c
572
src/eval.c
|
|
@ -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);
|
||||
|
|
|
|||
29
src/gc.c
29
src/gc.c
|
|
@ -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);
|
||||
|
|
|
|||
4
src/gc.h
4
src/gc.h
|
|
@ -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
79
src/matcher.c
Normal 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
27
src/matcher.h
Normal 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
|
||||
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue