#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 *); 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_assignable_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_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 compile_assignable_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_assignable_constant( struct compiler *compiler, struct apfl_expr_const *constant, struct compile_assignable_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; } static bool compile_assignable_predicate( struct compiler *compiler, bool local, struct apfl_position position, struct apfl_expr_assignable_predicate *predicate, struct compile_assignable_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_assignable(compiler, local, position, predicate->lhs, ilists); } static bool compile_assignable_list_expand( struct compiler *compiler, bool local, struct apfl_position position, struct apfl_expr_assignable_list *list, 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, ilists.matcher, 1)); milist_put_insn(ilists.matcher, 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, ilists)); } TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); milist_put_insn(ilists.matcher, MATCHER_REMAINDING); TRY(compile_assignable(compiler, local, position, &expand_item->assignable, ilists)); return true; } static bool compile_assignable_list( struct compiler *compiler, bool local, struct apfl_position position, struct apfl_expr_assignable_list *list, struct compile_assignable_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++) { struct apfl_expr_assignable_list_item *item = &list->items[i]; if (item->expand) { return compile_assignable_list_expand( compiler, local, position, list, ilists, i ); } TRY(compile_assignable(compiler, local, position, &item->assignable, ilists)); } TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); milist_put_insn(ilists.matcher, MATCHER_LEAVE_LIST); return true; } static bool compile_assignable( struct compiler *compiler, bool local, struct apfl_position position, struct apfl_expr_assignable *assignable, 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, ilists); case APFL_EXPR_ASSIGNABLE_CONSTANT: return compile_assignable_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: TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); milist_put_insn(ilists.matcher, 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 compile_assignable_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; 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; }