788 lines
24 KiB
C
788 lines
24 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;
|
|
}
|
|
|
|
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
|
|
) {
|
|
// 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 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(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;
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
return false;
|
|
}
|
|
|
|
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)));
|
|
|
|
// Drop the argument list, we ignore it in simple functions
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
|
|
ilist_put_insn(body_ilist, INSN_DROP);
|
|
|
|
TRY(compile_body(compiler, func, body_ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_FUNC);
|
|
ilist_put_body(ilist, body_ilist);
|
|
|
|
return true;
|
|
}
|
|
|
|
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_complex_func_inner(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line)
|
|
{
|
|
if (func->len != 1) {
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
return false;
|
|
}
|
|
|
|
struct apfl_expr_subfunc *subfunc = &func->subfuncs[0];
|
|
|
|
struct instruction_list *body_ilist = NULL;
|
|
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line)));
|
|
|
|
for (size_t i = 0; i < subfunc->params.len; i++) {
|
|
struct apfl_expr_params_item *param = &subfunc->params.params[i];
|
|
if (param->expand) {
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
return false;
|
|
}
|
|
|
|
switch (param->param.type) {
|
|
case APFL_EXPR_PARAM_VAR:
|
|
{
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 4));
|
|
|
|
struct apfl_string *str;
|
|
TRY(string_move_into_new(compiler, &str, ¶m->param.var));
|
|
|
|
ilist_put_insn(body_ilist, INSN_GET_BY_INDEX_KEEP);
|
|
ilist_put_index(body_ilist, i);
|
|
ilist_put_insn(body_ilist, INSN_MOVE_TO_LOCAL_VAR);
|
|
ilist_put_string(body_ilist, str);
|
|
break;
|
|
}
|
|
case APFL_EXPR_PARAM_CONSTANT:
|
|
case APFL_EXPR_PARAM_PREDICATE:
|
|
case APFL_EXPR_PARAM_LIST:
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
return false;
|
|
case APFL_EXPR_PARAM_BLANK:
|
|
break;
|
|
}
|
|
}
|
|
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
|
|
ilist_put_insn(body_ilist, INSN_DROP); // Drop the argument list
|
|
|
|
TRY(compile_body(compiler, &subfunc->body, body_ilist));
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
ilist_put_insn(ilist, INSN_FUNC);
|
|
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, size_t line)
|
|
{
|
|
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
|
|
bool ok = compile_complex_func_inner(compiler, func, ilist, line);
|
|
apfl_gc_tmproots_restore(compiler->gc, tmproots);
|
|
return ok;
|
|
}
|
|
|
|
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, new_line);
|
|
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;
|
|
}
|