apfl/src/compile.c

962 lines
35 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 *, struct apfl_string *name);
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
));
}
#define MILIST_PUT(milist, member, value) \
assert(milist->cap > 0); \
assert(milist->len < milist->cap); \
\
milist->instructions[milist->len] = (union matcher_instruction_or_arg) { \
.member = value, \
}; \
milist->len++;
static void
milist_put_insn(struct matcher_instruction_list *milist, enum matcher_instruction instruction)
{
MILIST_PUT(milist, instruction, instruction)
}
static void
milist_put_index(struct matcher_instruction_list *milist, size_t index)
{
MILIST_PUT(milist, index, index)
}
static void
milist_put_len(struct matcher_instruction_list *milist, size_t len)
{
MILIST_PUT(milist, len, len)
}
static void
milist_put_string(struct matcher_instruction_list *milist, struct apfl_string *string)
{
MILIST_PUT(milist, string, string)
}
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, NULL));
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, NULL));
TRY(compile_expr(compiler, pair->v, ilist, NULL));
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, NULL));
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, NULL));
TRY(compile_expr(compiler, at->rhs, ilist, NULL));
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, str));
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 apfl_string *filename)
{
struct instruction_list *ilist;
if (
(ilist = apfl_instructions_new(compiler->gc, line, filename)) == 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_args {
struct instruction_list *ilist;
struct matcher_instruction_list *milist;
bool local;
};
static bool
compile_assignable(
struct compiler *compiler,
struct apfl_position position,
struct apfl_expr_assignable *assignable,
struct compile_matchable_args args
);
static enum matcher_instruction
matcher_capture_to_var(bool local)
{
return local ? MATCHER_CAPTURE_TO_VAR_LOCAL : MATCHER_CAPTURE_TO_VAR;
}
static enum matcher_instruction
matcher_capture_to_var_with_path(bool local)
{
return local ? MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH : MATCHER_CAPTURE_TO_VAR_WITH_PATH;
}
static enum instruction
insn_var_new(bool local)
{
return local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW;
}
static bool
compile_assignable_var_path(
struct compiler *compiler,
struct apfl_expr_assignable_var_or_member *var_or_member,
struct compile_matchable_args args,
size_t *path_len,
struct apfl_string **varname
) {
size_t index;
struct apfl_string *dot_rhs = NULL;
switch (var_or_member->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
TRY(ilist_ensure_cap(compiler, args.ilist, 2));
TRY(string_move_into_new(compiler, varname, &var_or_member->var));
ilist_put_insn(args.ilist, insn_var_new(args.local));
ilist_put_string(args.ilist, *varname);
return true;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
TRY(compile_assignable_var_path(compiler, var_or_member->dot.lhs, args, path_len, varname));
index = args.milist->value_count++;
++*path_len;
TRY(ilist_ensure_cap(compiler, args.ilist, 4));
TRY(string_move_into_new(compiler, &dot_rhs, &var_or_member->dot.rhs));
ilist_put_insn(args.ilist, INSN_STRING);
ilist_put_string(args.ilist, dot_rhs);
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL);
ilist_put_insn(args.ilist, index);
return true;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
TRY(compile_assignable_var_path(compiler, var_or_member->dot.lhs, args, path_len, varname));
index = args.milist->value_count++;
++*path_len;
TRY(compile_expr(compiler, var_or_member->at.rhs, args.ilist, NULL));
TRY(ilist_ensure_cap(compiler, args.ilist, 2));
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL);
ilist_put_index(args.ilist, index);
return true;
}
assert(false);
return false;
}
static bool
compile_assignable_var_or_member(
struct compiler *compiler,
struct apfl_expr_assignable_var_or_member *var_or_member,
struct compile_matchable_args args
) {
/* 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
* }
*/
args.milist->capture_count++;
size_t path_start = args.milist->value_count;
size_t path_len = 0;
struct apfl_string *varname = NULL;
TRY(compile_assignable_var_path(compiler, var_or_member, args, &path_len, &varname));
assert(varname != NULL /* if compile_assignable_var_path succeeded, it should have set varname */);
if (path_len == 0) {
TRY(milist_ensure_cap(compiler, args.milist, 2));
milist_put_insn(args.milist, matcher_capture_to_var(args.local));
milist_put_string(args.milist, varname);
} else {
TRY(milist_ensure_cap(compiler, args.milist, 4));
milist_put_insn(args.milist, matcher_capture_to_var_with_path(args.local));
milist_put_string(args.milist, varname);
milist_put_index(args.milist, path_start);
milist_put_len(args.milist, path_len);
}
return true;
}
static bool
compile_matchable_constant(
struct compiler *compiler,
struct apfl_expr_const *constant,
struct compile_matchable_args args
) {
size_t index = args.milist->value_count++;
TRY(compile_constant(compiler, constant, args.ilist));
TRY(ilist_ensure_cap(compiler, args.ilist, 2));
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL);
ilist_put_index(args.ilist, index);
TRY(milist_ensure_cap(compiler, args.milist, 3));
milist_put_insn(args.milist, MATCHER_CHECK_CONST);
milist_put_index(args.milist, index);
milist_put_insn(args.milist, MATCHER_IGNORE);
return true;
}
#define DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(name, compile_matchable, predtype) \
static bool \
name( \
struct compiler *compiler, \
struct apfl_position position, \
predtype *predicate, \
struct compile_matchable_args args \
) { \
size_t index = args.milist->value_count++; \
\
TRY(compile_expr(compiler, predicate->rhs, args.ilist, NULL)); \
TRY(ilist_ensure_cap(compiler, args.ilist, 2)); \
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL); \
ilist_put_index(args.ilist, index); \
\
TRY(milist_ensure_cap(compiler, args.milist, 2)); \
milist_put_insn(args.milist, MATCHER_CHECK_PRED); \
milist_put_index(args.milist, index); \
\
return compile_matchable(compiler, position, predicate->lhs, args); \
} \
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, \
struct apfl_position position, \
listtype *list, \
struct compile_matchable_args args, \
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, args.milist, 1)); \
milist_put_insn(args.milist, 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, position, &item->listitemmemb, args)); \
} \
\
TRY(milist_ensure_cap(compiler, args.milist, 1)); \
milist_put_insn(args.milist, MATCHER_REMAINDING); \
\
TRY(compile_matchable(compiler, position, &expand_item->listitemmemb, args)); \
\
return true; \
} \
\
static bool \
name( \
struct compiler *compiler, \
struct apfl_position position, \
listtype *list, \
struct compile_matchable_args args \
) { \
TRY(milist_ensure_cap(compiler, args.milist, 1)); \
milist_put_insn(args.milist, MATCHER_ENTER_LIST); \
\
for (size_t i = 0; i < list->len; i++) { \
listitemtype *item = &list->listmemb[i]; \
\
if (item->expand) { \
return name##_expand( \
compiler, \
position, \
list, \
args, \
i \
); \
} \
\
TRY(compile_matchable(compiler, position, &item->listitemmemb, args)); \
} \
\
TRY(milist_ensure_cap(compiler, args.milist, 1)); \
milist_put_insn(args.milist, 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 matcher_instruction_list *milist)
{
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_IGNORE);
return true;
}
static bool
compile_assignable(
struct compiler *compiler,
struct apfl_position position,
struct apfl_expr_assignable *assignable,
struct compile_matchable_args args
) {
switch (assignable->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
return compile_assignable_var_or_member(compiler, &assignable->var_or_member, args);
case APFL_EXPR_ASSIGNABLE_CONSTANT:
return compile_matchable_constant(compiler, &assignable->constant, args);
case APFL_EXPR_ASSIGNABLE_PREDICATE:
return compile_assignable_predicate(compiler, position, &assignable->predicate, args);
case APFL_EXPR_ASSIGNABLE_LIST:
return compile_assignable_list(compiler, position, &assignable->list, args);
case APFL_EXPR_ASSIGNABLE_BLANK:
return compile_matchable_blank(compiler, args.milist);
}
assert(false);
return false;
}
static bool
compile_assignment_lhs(
struct compiler *compiler,
struct apfl_expr_assignable *assignable,
bool local,
struct apfl_position position,
struct instruction_list *ilist
) {
// By ensuring the capacity early, we can avoid tmprooting the milist, since
// we can immediately append it to the already GC-rooted ilist.
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct matcher_instruction_list *milist = apfl_matcher_instructions_new(compiler->gc);
MALLOC_FAIL_IF_NULL(compiler, milist);
ilist_put_insn(ilist, INSN_MATCHER_PUSH);
ilist_put_matcher(ilist, milist);
return compile_assignable(compiler, position, assignable, (struct compile_matchable_args) {
.ilist = ilist,
.milist = milist,
.local = local,
});
}
static bool
compile_complex_assignment(
struct compiler *compiler,
struct apfl_expr_assignment *assignment,
struct apfl_position position,
struct instruction_list *ilist
) {
TRY(compile_assignment_lhs(
compiler,
&assignment->lhs,
assignment->local,
position,
ilist
));
TRY(compile_expr(compiler, assignment->rhs, ilist, NULL));
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_DUP);
ilist_put_insn(ilist, INSN_MATCHER_MUST_MATCH);
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, NULL));
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)
{
// TODO: This leaves the results of all expressions on the value stack.
// The runtime will clean this up, but we van be less wasteful here.
for (size_t i = 0; i < body->len; i++) {
TRY(compile_expr(compiler, &body->items[i], ilist, NULL));
}
return true;
}
static bool
compile_simple_func_inner(
struct compiler *compiler,
struct apfl_expr_body *func,
struct instruction_list *ilist,
size_t line,
struct apfl_string *name
) {
struct instruction_list *body_ilist = NULL;
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line, ilist->filename)));
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
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_PUSH);
ilist_put_matcher(ilist, milist);
ilist_put_insn(ilist, INSN_FUNC_ADD_SUBFUNC);
ilist_put_body(ilist, body_ilist);
if (name != NULL) {
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_FUNC_SET_NAME);
ilist_put_string(ilist, name);
}
return true;
}
static bool
compile_param(
struct compiler *compiler,
struct apfl_position position,
struct apfl_expr_param *param,
struct compile_matchable_args args
);
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_args args
) {
args.milist->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, args.milist, 2));
struct apfl_string *varname = NULL;
TRY(string_move_into_new(compiler, &varname, var));
milist_put_insn(args.milist, MATCHER_CAPTURE_TO_VAR_LOCAL);
milist_put_string(args.milist, varname);
return true;
}
static bool
compile_param(
struct compiler *compiler,
struct apfl_position position,
struct apfl_expr_param *param,
struct compile_matchable_args args
) {
switch (param->type) {
case APFL_EXPR_PARAM_VAR:
return compile_param_var(compiler, &param->var, args);
case APFL_EXPR_PARAM_CONSTANT:
return compile_matchable_constant(compiler, &param->constant, args);
case APFL_EXPR_PARAM_LIST:
return compile_param_list(compiler, position, &param->list, args);
case APFL_EXPR_PARAM_PREDICATE:
return compile_param_predicate(compiler, position, &param->predicate, args);
case APFL_EXPR_PARAM_BLANK:
return compile_matchable_blank(compiler, args.milist);
}
assert(false);
return false;
}
static bool
compile_simple_func(
struct compiler *compiler,
struct apfl_expr_body *func,
struct instruction_list *ilist,
size_t line,
struct apfl_string *name
) {
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
bool ok = compile_simple_func_inner(compiler, func, ilist, line, name);
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, ilist->filename)));
struct matcher_instruction_list *milist = NULL;
MALLOC_FAIL_IF_NULL(compiler, (milist = tmp_milist(compiler)));
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_MATCHER_PUSH);
ilist_put_matcher(ilist, milist);
TRY(compile_param_list(compiler, position, &subfunc->params, (struct compile_matchable_args) {
.ilist = ilist,
.milist = milist,
.local = true,
}));
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,
struct apfl_string *name
) {
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);
}
if (name != NULL) {
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_FUNC_SET_NAME);
ilist_put_string(ilist, name);
}
return true;
}
static bool
compile_expr(
struct compiler *compiler,
struct apfl_expr *expr,
struct instruction_list *ilist,
struct apfl_string *name
) {
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, name);
case APFL_EXPR_COMPLEX_FUNC:
return compile_complex_func(compiler, &expr->complex_func, ilist, expr->position, name);
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, NULL);
apfl_expr_deinit(gc->allocator, &expr);
if (!ok) {
*error_out = compiler.error;
}
return ok;
}