897 lines
34 KiB
C
897 lines
34 KiB
C
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "apfl.h"
|
|
#include "compile.h"
|
|
#include "bytecode.h"
|
|
#include "resizable.h"
|
|
#include "strings.h"
|
|
|
|
struct compiler {
|
|
struct gc *gc;
|
|
struct apfl_error error;
|
|
size_t line;
|
|
};
|
|
|
|
#define TRY(b) do { if (!(b)) { return false; } } while(0)
|
|
#define MALLOC_FAIL_IF_NULL(compiler, x) \
|
|
do { \
|
|
if ((x) == NULL) { \
|
|
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
static bool compile_expr(struct compiler *, struct apfl_expr *, struct instruction_list *);
|
|
|
|
static bool
|
|
malloc_failure_on_false(struct compiler *compiler, bool b)
|
|
{
|
|
if (!b) {
|
|
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
static bool
|
|
ilist_ensure_cap(struct compiler *compiler, struct instruction_list *ilist, size_t n)
|
|
{
|
|
return malloc_failure_on_false(compiler, apfl_resizable_ensure_cap_for_more_elements(
|
|
compiler->gc->allocator,
|
|
sizeof(union instruction_or_arg),
|
|
(void **)&ilist->instructions,
|
|
ilist->len,
|
|
&ilist->cap,
|
|
n
|
|
));
|
|
}
|
|
|
|
#define ILIST_PUT(ilist, member, data, dbgfmt, ...) \
|
|
assert(ilist->cap > 0); \
|
|
assert(ilist->len < ilist->cap); \
|
|
\
|
|
ilist->instructions[ilist->len] = (union instruction_or_arg) { \
|
|
.member = data, \
|
|
}; \
|
|
ilist->len++;
|
|
|
|
static void
|
|
ilist_put_insn(struct instruction_list *ilist, enum instruction instruction)
|
|
{
|
|
ILIST_PUT(ilist, instruction, instruction, "put_insn: %s", apfl_instruction_to_string(instruction))
|
|
}
|
|
|
|
static void
|
|
ilist_put_number(struct instruction_list *ilist, apfl_number number)
|
|
{
|
|
ILIST_PUT(ilist, number, number, "put_number: %lf", number)
|
|
}
|
|
|
|
static void
|
|
ilist_put_count(struct instruction_list *ilist, size_t count)
|
|
{
|
|
ILIST_PUT(ilist, count, count, "put_count: %d", (int)count)
|
|
}
|
|
|
|
static void
|
|
ilist_put_string(struct instruction_list *ilist, struct apfl_string *string)
|
|
{
|
|
ILIST_PUT(ilist, string, string, "put_string: " APFL_STR_FMT, APFL_STR_FMT_ARGS(*string))
|
|
}
|
|
|
|
static void
|
|
ilist_put_body(struct instruction_list *ilist, struct instruction_list *body)
|
|
{
|
|
ILIST_PUT(ilist, body, body, "put_body: %p", (void *)body)
|
|
}
|
|
|
|
static void
|
|
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++;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
|
|
static bool
|
|
string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in)
|
|
{
|
|
struct apfl_string *str = apfl_string_move_into_new_gc_string(compiler->gc, in);
|
|
if (str == NULL) {
|
|
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
|
|
return false;
|
|
}
|
|
|
|
*out = str;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_constant(struct compiler *compiler, struct apfl_expr_const *constant, struct instruction_list *ilist)
|
|
{
|
|
switch (constant->type) {
|
|
case APFL_EXPR_CONST_NIL:
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, INSN_NIL);
|
|
return true;
|
|
case APFL_EXPR_CONST_BOOLEAN:
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, constant->boolean ? INSN_TRUE : INSN_FALSE);
|
|
return true;
|
|
case APFL_EXPR_CONST_NUMBER:
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_NUMBER);
|
|
ilist_put_number(ilist, constant->number);
|
|
return true;
|
|
case APFL_EXPR_CONST_STRING:
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
struct apfl_string *str;
|
|
TRY(string_move_into_new(compiler, &str, &constant->string));
|
|
|
|
ilist_put_insn(ilist, INSN_STRING);
|
|
ilist_put_string(ilist, str);
|
|
return true;
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
compile_list(struct compiler *compiler, struct apfl_expr_list *list, struct instruction_list *ilist)
|
|
{
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_LIST);
|
|
ilist_put_count(ilist, list->len);
|
|
|
|
for (size_t i = 0; i < list->len; i++) {
|
|
struct apfl_expr_list_item *item = &list->items[i];
|
|
|
|
TRY(compile_expr(compiler, item->expr, ilist));
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, item->expand ? INSN_LIST_EXPAND_INTO : INSN_LIST_APPEND);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_dict(struct compiler *compiler, struct apfl_expr_dict *dict, struct instruction_list *ilist)
|
|
{
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, INSN_DICT);
|
|
|
|
for (size_t i = 0; i < dict->len; i++) {
|
|
struct apfl_expr_dict_pair *pair = &dict->items[i];
|
|
|
|
TRY(compile_expr(compiler, pair->k, ilist));
|
|
TRY(compile_expr(compiler, pair->v, ilist));
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, INSN_DICT_APPEND_KVPAIR);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_dot(struct compiler *compiler, struct apfl_expr_dot *dot, struct instruction_list *ilist)
|
|
{
|
|
TRY(compile_expr(compiler, dot->lhs, ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 3));
|
|
|
|
struct apfl_string *str;
|
|
TRY(string_move_into_new(compiler, &str, &dot->rhs));
|
|
|
|
ilist_put_insn(ilist, INSN_STRING);
|
|
ilist_put_string(ilist, str);
|
|
ilist_put_insn(ilist, INSN_GET_MEMBER);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_at(struct compiler *compiler, struct apfl_expr_at *at, struct instruction_list *ilist)
|
|
{
|
|
TRY(compile_expr(compiler, at->lhs, ilist));
|
|
TRY(compile_expr(compiler, at->rhs, ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, INSN_GET_MEMBER);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_var(struct compiler *compiler, struct apfl_string *var, struct instruction_list *ilist)
|
|
{
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
struct apfl_string *str;
|
|
TRY(string_move_into_new(compiler, &str, var));
|
|
|
|
ilist_put_insn(ilist, INSN_VAR_GET);
|
|
ilist_put_string(ilist, str);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_simple_assignment(
|
|
struct compiler *compiler,
|
|
struct apfl_string *var,
|
|
struct apfl_expr *rhs,
|
|
struct instruction_list *ilist,
|
|
enum instruction new_insn,
|
|
enum instruction set_insn
|
|
) {
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
struct apfl_string *str;
|
|
TRY(string_move_into_new(compiler, &str, var));
|
|
|
|
ilist_put_insn(ilist, new_insn);
|
|
ilist_put_string(ilist, str);
|
|
|
|
TRY(compile_expr(compiler, rhs, ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, set_insn);
|
|
ilist_put_string(ilist, str);
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct instruction_list *
|
|
tmp_ilist(struct compiler *compiler, int line)
|
|
{
|
|
struct instruction_list *ilist;
|
|
if (
|
|
(ilist = apfl_instructions_new(compiler->gc, line)) == NULL
|
|
|| !apfl_gc_tmproot_add(
|
|
compiler->gc,
|
|
GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS)
|
|
)
|
|
) {
|
|
return NULL;
|
|
}
|
|
|
|
return ilist;
|
|
}
|
|
|
|
static struct matcher_instruction_list *
|
|
tmp_milist(struct compiler *compiler)
|
|
{
|
|
struct matcher_instruction_list *milist;
|
|
if (
|
|
(milist = apfl_matcher_instructions_new(compiler->gc)) == NULL
|
|
|| !apfl_gc_tmproot_add(
|
|
compiler->gc,
|
|
GC_OBJECT_FROM(milist, GC_TYPE_MATCHER_INSTRUCTIONS)
|
|
)
|
|
) {
|
|
return NULL;
|
|
}
|
|
|
|
return milist;
|
|
}
|
|
|
|
struct compile_matchable_ilists {
|
|
struct instruction_list *prelude;
|
|
struct instruction_list *newvars;
|
|
struct instruction_list *setvars;
|
|
struct matcher_instruction_list *matcher;
|
|
};
|
|
|
|
static bool
|
|
compile_assignable(
|
|
struct compiler *compiler,
|
|
bool local,
|
|
struct apfl_position position,
|
|
struct apfl_expr_assignable *assignable,
|
|
struct compile_matchable_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 compile_matchable_ilists ilists
|
|
) {
|
|
size_t index = ilists.matcher->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, ilists.matcher, 2));
|
|
milist_put_insn(ilists.matcher, MATCHER_CAPTURE);
|
|
milist_put_index(ilists.matcher, 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_matchable_constant(
|
|
struct compiler *compiler,
|
|
struct apfl_expr_const *constant,
|
|
struct compile_matchable_ilists ilists
|
|
) {
|
|
size_t index = ilists.matcher->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, ilists.matcher, 3));
|
|
milist_put_insn(ilists.matcher, MATCHER_CHECK_CONST);
|
|
milist_put_index(ilists.matcher, index);
|
|
milist_put_insn(ilists.matcher, MATCHER_IGNORE);
|
|
|
|
return true;
|
|
}
|
|
|
|
#define DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(name, compile_matchable, predtype) \
|
|
static bool \
|
|
name( \
|
|
struct compiler *compiler, \
|
|
bool local, \
|
|
struct apfl_position position, \
|
|
predtype *predicate, \
|
|
struct compile_matchable_ilists ilists \
|
|
) { \
|
|
size_t index = ilists.matcher->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, ilists.matcher, 2)); \
|
|
milist_put_insn(ilists.matcher, MATCHER_CHECK_PRED); \
|
|
milist_put_index(ilists.matcher, index); \
|
|
\
|
|
return compile_matchable(compiler, local, position, predicate->lhs, ilists); \
|
|
} \
|
|
|
|
DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(
|
|
compile_assignable_predicate,
|
|
compile_assignable,
|
|
struct apfl_expr_assignable_predicate
|
|
)
|
|
|
|
#define DEF_COMPILE_ABSTRACT_MATCHABLE_LIST(name, compile_matchable, listtype, listmemb, listitemtype, listitemmemb) \
|
|
static bool \
|
|
name##_expand( \
|
|
struct compiler *compiler, \
|
|
bool local, \
|
|
struct apfl_position position, \
|
|
listtype *list, \
|
|
struct compile_matchable_ilists ilists, \
|
|
size_t expand_at \
|
|
) { \
|
|
assert(expand_at < list->len); \
|
|
listitemtype *expand_item = &list->listmemb[expand_at]; \
|
|
assert(expand_item->expand); \
|
|
\
|
|
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
|
|
milist_put_insn(ilists.matcher, MATCHER_CONTINUE_FROM_END); \
|
|
\
|
|
for (size_t i = list->len; i-- > expand_at+1; ) { \
|
|
listitemtype *item = &list->listmemb[i]; \
|
|
\
|
|
if (item->expand) { \
|
|
compiler->error = (struct apfl_error) { \
|
|
.type = APFL_ERR_ONLY_ONE_EXPAND_ALLOWED, \
|
|
.position = position, \
|
|
}; \
|
|
return false; \
|
|
} \
|
|
\
|
|
TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \
|
|
} \
|
|
\
|
|
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
|
|
milist_put_insn(ilists.matcher, MATCHER_REMAINDING); \
|
|
\
|
|
TRY(compile_matchable(compiler, local, position, &expand_item->listitemmemb, ilists)); \
|
|
\
|
|
return true; \
|
|
} \
|
|
\
|
|
static bool \
|
|
name( \
|
|
struct compiler *compiler, \
|
|
bool local, \
|
|
struct apfl_position position, \
|
|
listtype *list, \
|
|
struct compile_matchable_ilists ilists \
|
|
) { \
|
|
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
|
|
milist_put_insn(ilists.matcher, MATCHER_ENTER_LIST); \
|
|
\
|
|
for (size_t i = 0; i < list->len; i++) { \
|
|
listitemtype *item = &list->listmemb[i]; \
|
|
\
|
|
if (item->expand) { \
|
|
return name##_expand( \
|
|
compiler, \
|
|
local, \
|
|
position, \
|
|
list, \
|
|
ilists, \
|
|
i \
|
|
); \
|
|
} \
|
|
\
|
|
TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \
|
|
} \
|
|
\
|
|
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
|
|
milist_put_insn(ilists.matcher, MATCHER_LEAVE_LIST); \
|
|
return true; \
|
|
} \
|
|
|
|
DEF_COMPILE_ABSTRACT_MATCHABLE_LIST(
|
|
compile_assignable_list,
|
|
compile_assignable,
|
|
struct apfl_expr_assignable_list,
|
|
items,
|
|
struct apfl_expr_assignable_list_item,
|
|
assignable
|
|
)
|
|
|
|
static bool
|
|
compile_matchable_blank(struct compiler *compiler, struct compile_matchable_ilists ilists)
|
|
{
|
|
TRY(milist_ensure_cap(compiler, ilists.matcher, 1));
|
|
milist_put_insn(ilists.matcher, MATCHER_IGNORE);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_assignable(
|
|
struct compiler *compiler,
|
|
bool local,
|
|
struct apfl_position position,
|
|
struct apfl_expr_assignable *assignable,
|
|
struct compile_matchable_ilists ilists
|
|
) {
|
|
switch (assignable->type) {
|
|
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
|
|
return compile_assignable_var_or_member(compiler, local, &assignable->var_or_member, ilists);
|
|
case APFL_EXPR_ASSIGNABLE_CONSTANT:
|
|
return compile_matchable_constant(compiler, &assignable->constant, ilists);
|
|
case APFL_EXPR_ASSIGNABLE_PREDICATE:
|
|
return compile_assignable_predicate(compiler, local, position, &assignable->predicate, ilists);
|
|
case APFL_EXPR_ASSIGNABLE_LIST:
|
|
return compile_assignable_list(compiler, local, position, &assignable->list, ilists);
|
|
case APFL_EXPR_ASSIGNABLE_BLANK:
|
|
return compile_matchable_blank(compiler, ilists);
|
|
}
|
|
|
|
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
|
|
) {
|
|
// TODO: This is not entirely correct: By evaluating the RHS first, it might
|
|
// affect variables used in the matcher. This is only really important when
|
|
// predicates are used, which are not yet implemented:
|
|
//
|
|
// x = {true}
|
|
// foo?x = ({x={false}})
|
|
//
|
|
// This should succeed: The predicate x should be loaded before the
|
|
// evaluation of the rhs occurs. With our current implementation it will
|
|
// fail, though.
|
|
TRY(compile_expr(compiler, assignment->rhs, ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
struct compile_matchable_ilists ilists = {
|
|
.prelude = ilist,
|
|
};
|
|
|
|
MALLOC_FAIL_IF_NULL(compiler, (ilists.matcher = tmp_milist(compiler)));
|
|
MALLOC_FAIL_IF_NULL(compiler, (ilists.newvars = tmp_ilist(compiler, position.line)));
|
|
MALLOC_FAIL_IF_NULL(compiler, (ilists.setvars = tmp_ilist(compiler, position.line)));
|
|
|
|
ilist_put_insn(ilist, INSN_MATCHER_LOAD);
|
|
ilist_put_matcher(ilist, ilists.matcher);
|
|
|
|
TRY(compile_assignable(compiler, assignment->local, position, &assignment->lhs, ilists));
|
|
|
|
TRY(concat_ilists(compiler, ilist, ilists.newvars));
|
|
|
|
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 (
|
|
assignment->lhs.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER
|
|
&& assignment->lhs.var_or_member.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR
|
|
) {
|
|
return compile_simple_assignment(
|
|
compiler,
|
|
&assignment->lhs.var_or_member.var,
|
|
assignment->rhs,
|
|
ilist,
|
|
assignment->local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW,
|
|
assignment->local ? INSN_VAR_SET_LOCAL : INSN_VAR_SET
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static bool
|
|
compile_call(struct compiler *compiler, struct apfl_expr_call *call, struct instruction_list *ilist)
|
|
{
|
|
TRY(compile_expr(compiler, call->callee, ilist));
|
|
TRY(compile_list(compiler, &call->arguments, ilist));
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, INSN_CALL);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_body(struct compiler *compiler, struct apfl_expr_body *body, struct instruction_list *ilist)
|
|
{
|
|
for (size_t i = 0; i < body->len; i++) {
|
|
TRY(compile_expr(compiler, &body->items[i], ilist));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line)
|
|
{
|
|
struct instruction_list *body_ilist = NULL;
|
|
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line)));
|
|
|
|
struct matcher_instruction_list *milist = NULL;
|
|
MALLOC_FAIL_IF_NULL(compiler, (milist = tmp_milist(compiler)));
|
|
|
|
TRY(milist_ensure_cap(compiler, milist, 1));
|
|
milist_put_insn(milist, MATCHER_IGNORE); // Ignore all arguments
|
|
|
|
// Drop the matcher, we ignore arguments in simple functions
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
|
|
ilist_put_insn(body_ilist, INSN_MATCHER_DROP);
|
|
|
|
TRY(compile_body(compiler, func, body_ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 6));
|
|
ilist_put_insn(ilist, INSN_FUNC);
|
|
ilist_put_count(ilist, 1);
|
|
ilist_put_insn(ilist, INSN_MATCHER_LOAD);
|
|
ilist_put_matcher(ilist, milist);
|
|
ilist_put_insn(ilist, INSN_FUNC_ADD_SUBFUNC);
|
|
ilist_put_body(ilist, body_ilist);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_param(
|
|
struct compiler *compiler,
|
|
bool local,
|
|
struct apfl_position position,
|
|
struct apfl_expr_param *param,
|
|
struct compile_matchable_ilists ilists
|
|
);
|
|
|
|
DEF_COMPILE_ABSTRACT_MATCHABLE_LIST(
|
|
compile_param_list,
|
|
compile_param,
|
|
struct apfl_expr_params,
|
|
params,
|
|
struct apfl_expr_params_item,
|
|
param
|
|
)
|
|
|
|
DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(
|
|
compile_param_predicate,
|
|
compile_param,
|
|
struct apfl_expr_param_predicate
|
|
)
|
|
|
|
static bool
|
|
compile_param_var(
|
|
struct compiler *compiler,
|
|
struct apfl_string *var,
|
|
struct compile_matchable_ilists ilists
|
|
) {
|
|
size_t index = ilists.matcher->capture_count++;
|
|
|
|
/* 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, ilists.matcher, 2));
|
|
milist_put_insn(ilists.matcher, MATCHER_CAPTURE);
|
|
milist_put_index(ilists.matcher, index);
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilists.setvars, 3));
|
|
|
|
struct apfl_string *varname = NULL;
|
|
TRY(string_move_into_new(compiler, &varname, var));
|
|
|
|
ilist_put_insn(ilists.setvars, INSN_VAR_SET_LOCAL_FROM_MATCHER);
|
|
ilist_put_string(ilists.setvars, varname);
|
|
ilist_put_index(ilists.setvars, index);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_param(
|
|
struct compiler *compiler,
|
|
bool local,
|
|
struct apfl_position position,
|
|
struct apfl_expr_param *param,
|
|
struct compile_matchable_ilists ilists
|
|
) {
|
|
(void)local;
|
|
|
|
switch (param->type) {
|
|
case APFL_EXPR_PARAM_VAR:
|
|
return compile_param_var(compiler, ¶m->var, ilists);
|
|
case APFL_EXPR_PARAM_CONSTANT:
|
|
return compile_matchable_constant(compiler, ¶m->constant, ilists);
|
|
case APFL_EXPR_PARAM_LIST:
|
|
return compile_param_list(compiler, true, position, ¶m->list, ilists);
|
|
case APFL_EXPR_PARAM_PREDICATE:
|
|
return compile_param_predicate(compiler, true, position, ¶m->predicate, ilists);
|
|
case APFL_EXPR_PARAM_BLANK:
|
|
return compile_matchable_blank(compiler, ilists);
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
compile_simple_func(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line)
|
|
{
|
|
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
|
|
bool ok = compile_simple_func_inner(compiler, func, ilist, line);
|
|
apfl_gc_tmproots_restore(compiler->gc, tmproots);
|
|
return ok;
|
|
}
|
|
|
|
static bool
|
|
compile_subfunc(struct compiler *compiler, struct apfl_expr_subfunc *subfunc, struct instruction_list *ilist, struct apfl_position position)
|
|
{
|
|
struct instruction_list *body_ilist = NULL;
|
|
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, position.line)));
|
|
|
|
struct matcher_instruction_list *matcher = NULL;
|
|
MALLOC_FAIL_IF_NULL(compiler, (matcher = tmp_milist(compiler)));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_MATCHER_LOAD);
|
|
ilist_put_matcher(ilist, matcher);
|
|
|
|
TRY(compile_param_list(compiler, true, position, &subfunc->params, (struct compile_matchable_ilists) {
|
|
.prelude = ilist,
|
|
.newvars = NULL, // We don't use newvars when compiling parameters
|
|
.setvars = body_ilist,
|
|
.matcher = matcher,
|
|
}));
|
|
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
|
|
ilist_put_insn(body_ilist, INSN_MATCHER_DROP);
|
|
|
|
TRY(compile_body(compiler, &subfunc->body, body_ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_FUNC_ADD_SUBFUNC);
|
|
ilist_put_body(ilist, body_ilist);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_complex_func(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, struct apfl_position position)
|
|
{
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_FUNC);
|
|
ilist_put_count(ilist, func->len);
|
|
|
|
for (size_t i = 0; i < func->len; i++) {
|
|
struct apfl_expr_subfunc *subfunc = &func->subfuncs[i];
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
|
|
bool ok = compile_subfunc(compiler, subfunc, ilist, position);
|
|
apfl_gc_tmproots_restore(compiler->gc, tmproots);
|
|
|
|
TRY(ok);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instruction_list *ilist)
|
|
{
|
|
size_t new_line = (size_t)expr->position.line;
|
|
if (new_line != compiler->line) {
|
|
if (new_line == compiler->line + 1) {
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, INSN_NEXT_LINE);
|
|
} else {
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_SET_LINE);
|
|
ilist_put_count(ilist, new_line);
|
|
}
|
|
|
|
compiler->line = new_line;
|
|
}
|
|
|
|
switch (expr->type) {
|
|
case APFL_EXPR_CALL:
|
|
return compile_call(compiler, &expr->call, ilist);
|
|
case APFL_EXPR_SIMPLE_FUNC:
|
|
return compile_simple_func(compiler, &expr->simple_func, ilist, new_line);
|
|
case APFL_EXPR_COMPLEX_FUNC:
|
|
return compile_complex_func(compiler, &expr->complex_func, ilist, expr->position);
|
|
case APFL_EXPR_CONSTANT:
|
|
return compile_constant(compiler, &expr->constant, ilist);
|
|
case APFL_EXPR_BLANK:
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
ilist_put_insn(ilist, INSN_NIL);
|
|
return true;
|
|
case APFL_EXPR_LIST:
|
|
return compile_list(compiler, &expr->list, ilist);
|
|
case APFL_EXPR_DICT:
|
|
return compile_dict(compiler, &expr->dict, ilist);
|
|
case APFL_EXPR_DOT:
|
|
return compile_dot(compiler, &expr->dot, ilist);
|
|
case APFL_EXPR_AT:
|
|
return compile_at(compiler, &expr->at, ilist);
|
|
case APFL_EXPR_VAR:
|
|
return compile_var(compiler, &expr->var, ilist);
|
|
case APFL_EXPR_ASSIGNMENT:
|
|
return compile_assignment(compiler, &expr->assignment, expr->position, ilist);
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
apfl_compile(struct gc *gc, struct apfl_expr expr, struct apfl_error *error_out, struct instruction_list *out)
|
|
{
|
|
struct compiler compiler = {
|
|
.gc = gc,
|
|
.line = out->line,
|
|
};
|
|
|
|
bool ok = compile_expr(&compiler, &expr, out);
|
|
apfl_expr_deinit(gc->allocator, &expr);
|
|
|
|
if (!ok) {
|
|
*error_out = compiler.error;
|
|
}
|
|
return ok;
|
|
}
|