#include #include #include #include #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_pair *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_pair(struct compiler *compiler, struct apfl_expr_pair *pair, struct instruction_list *ilist) { TRY(compile_expr(compiler, pair->lhs, ilist, NULL)); TRY(compile_expr(compiler, pair->rhs, ilist, NULL)); TRY(ilist_ensure_cap(compiler, ilist, 1)); ilist_put_insn(ilist, INSN_BUILD_PAIR); 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, size_t 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 ) #define DEF_COMPILE_ABSTRACT_MATCHABLE_PAIR(name, compile_matchable, pairtype) \ static bool \ name( \ struct compiler *compiler, \ struct apfl_position position, \ pairtype *pair, \ struct compile_matchable_args args \ ) { \ TRY(milist_ensure_cap(compiler, args.milist, 2)); \ milist_put_insn(args.milist, MATCHER_UNPACK_PAIR); \ \ TRY(compile_matchable(compiler, position, pair->lhs, args)); \ TRY(compile_matchable(compiler, position, pair->rhs, args)); \ \ return true; \ } DEF_COMPILE_ABSTRACT_MATCHABLE_PAIR( compile_assignable_pair, compile_assignable, struct apfl_expr_assignable_pair ) #define DEF_COMPILE_ABSTRACT_MATCHABLE_TAGGED(name, compile_matchable, taggedtype) \ static bool \ name( \ struct compiler *compiler, \ struct apfl_position position, \ taggedtype *tagged, \ struct compile_matchable_args args \ ) { \ size_t index = args.milist->value_count++; \ \ TRY(compile_expr(compiler, tagged->lhs, 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, 4)); \ milist_put_insn(args.milist, MATCHER_UNPACK_PAIR); \ milist_put_insn(args.milist, MATCHER_CHECK_CONST); \ milist_put_index(args.milist, index); \ milist_put_insn(args.milist, MATCHER_IGNORE); \ \ return compile_matchable(compiler, position, tagged->rhs, args); \ } DEF_COMPILE_ABSTRACT_MATCHABLE_TAGGED( compile_assignable_tagged, compile_assignable, struct apfl_expr_assignable_tagged ) 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_PAIR: return compile_assignable_pair(compiler, position, &assignable->pair, args); case APFL_EXPR_ASSIGNABLE_TAGGED: return compile_assignable_tagged(compiler, position, &assignable->tagged, 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))); 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_FUNC_ADD_SUBFUNC_ANYARGS); 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 ) DEF_COMPILE_ABSTRACT_MATCHABLE_PAIR( compile_param_pair, compile_param, struct apfl_expr_param_pair ) DEF_COMPILE_ABSTRACT_MATCHABLE_TAGGED( compile_param_tagged, compile_param, struct apfl_expr_param_tagged ) 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, ¶m->var, args); case APFL_EXPR_PARAM_CONSTANT: return compile_matchable_constant(compiler, ¶m->constant, args); case APFL_EXPR_PARAM_LIST: return compile_param_list(compiler, position, ¶m->list, args); case APFL_EXPR_PARAM_PREDICATE: return compile_param_predicate(compiler, position, ¶m->predicate, args); case APFL_EXPR_PARAM_PAIR: return compile_param_pair(compiler, position, ¶m->pair, args); case APFL_EXPR_PARAM_TAGGED: return compile_param_tagged(compiler, position, ¶m->tagged, 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_PAIR: return compile_pair(compiler, &expr->pair, 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; } bool apfl_compile_whole_file( struct gc *gc, struct apfl_parser *parser, struct apfl_error *error, struct instruction_list *out ) { for (;;) { switch (apfl_parser_next(parser)) { case APFL_PARSE_OK: { if (!apfl_compile( gc, apfl_parser_get_expr(parser), error, out )) { return false; } break; } case APFL_PARSE_ERROR: *error = apfl_parser_get_error(parser); return false; case APFL_PARSE_EOF: return true; } } }