From 53a331bc8b2fc33bd2b7e25a23b4128f89ee0b52 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 7 Jun 2019 10:47:47 +0200 Subject: Add assignment, while, extern, function signature type, start on bytecode --- src/ast.c | 210 ++++++++++++++++++++++++-- src/bytecode/bytecode.c | 212 +++++++++++++++++++++----- src/compiler.c | 3 +- src/parser.c | 392 ++++++++++++++++++++++++++++++++++-------------- src/ssa/ssa.c | 233 ++++++++++++++++++++++------ src/tokenizer.c | 111 ++++++++++++-- 6 files changed, 936 insertions(+), 225 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 983b3a4..4194737 100644 --- a/src/ast.c +++ b/src/ast.c @@ -44,6 +44,8 @@ BufferView ast_get_name(Ast *self) { case AST_IMPORT: case AST_STRING: case AST_BINOP: + case AST_IF_STATEMENT: + case AST_WHILE_STATEMENT: name = create_buffer_view_null(); break; case AST_NUMBER: @@ -52,6 +54,9 @@ BufferView ast_get_name(Ast *self) { case AST_LHS: name = self->value.lhs_expr->var_name; break; + case AST_ASSIGN: + name = ast_get_name(self->value.assign_expr->lhs_expr); + break; case AST_FUNCTION_CALL: name = self->value.func_call->func.name; break; @@ -69,7 +74,8 @@ static BufferView ast_get_code_reference(Ast *self) { return ast_get_name(self); } -int funcdecl_init(FunctionDecl *self, Scope *parent, ScopedAllocator *allocator) { +int funcdecl_init(FunctionDecl *self, FunctionSignature *signature, Scope *parent, ScopedAllocator *allocator) { + self->signature = signature; self->ssa_func_index = 0; return scope_init(&self->body, parent, allocator); } @@ -88,16 +94,23 @@ void structfield_init(StructField *self, BufferView name, BufferView type_name) variable_init(&self->type, type_name); } -int lhsexpr_init(LhsExpr *self, bool is_pub, bool is_const, BufferView var_name, ScopedAllocator *allocator) { +int lhsexpr_init(LhsExpr *self, bool is_extern, bool is_pub, bool is_const, BufferView var_name, ScopedAllocator *allocator) { + self->is_extern = is_extern; self->is_pub = is_pub; self->is_const = is_const; - variable_init(&self->type, create_buffer_view_null()); + self->type.type = VARIABLE_TYPE_NONE; + self->type.value.variable = NULL; self->var_name = var_name; self->rhs_expr = NULL; return_if_error(scoped_allocator_create_mutex(allocator, &self->mutex)); return 0; } +void assignmentexpr_init(AssignmentExpr *self, Ast *lhs_expr, Ast *rhs_expr) { + self->lhs_expr = lhs_expr; + self->rhs_expr = rhs_expr; +} + void import_init(Import *self, BufferView path) { self->path = path; self->file_scope = NULL; @@ -117,6 +130,7 @@ void number_init(Number *self, i64 value, bool is_integer, BufferView code_ref) void variable_init(Variable *self, BufferView name) { self->name = name; + self->resolved_var = NULL; } void binop_init(Binop *self) { @@ -126,6 +140,23 @@ void binop_init(Binop *self) { self->grouped = bool_false; } +int if_statement_init(IfStatement *self, Scope *parent, ScopedAllocator *allocator) { + self->condition = NULL; + self->else_if_stmt = NULL; + return scope_init(&self->body, parent, allocator); +} + +int else_if_statement_init(ElseIfStatement *self, Scope *parent, ScopedAllocator *allocator) { + self->condition = NULL; + self->next_else_if_stmt = NULL; + return scope_init(&self->body, parent, allocator); +} + +int while_statement_init(WhileStatement *self, Scope *parent, ScopedAllocator *allocator) { + self->condition = NULL; + return scope_init(&self->body, parent, allocator); +} + int scope_init(Scope *self, Scope *parent, ScopedAllocator *allocator) { return_if_error(buffer_init(&self->ast_objects, allocator)); return_if_error(hash_map_init(&self->named_objects, allocator, sizeof(Ast*), hash_compare_string, amal_hash_string)); @@ -195,17 +226,18 @@ static void parser_print_error(Parser *parser, const char *ref, const char *fmt, va_list args; va_start(args, fmt); if(parser) { - tokenizer_print_error(&parser->tokenizer, + tokenizer_print_error_args(&parser->tokenizer, tokenizer_get_code_reference_index(&parser->tokenizer, ref), fmt, args); } else { /* TODO: Redirect error to compiler error callback if set */ + amal_log_error("TODO: Fix this:"); amal_log_error(fmt, args); } va_end(args); } -static Ast* scope_get_resolved_variable(Scope *self, AstCompilerContext *context, BufferView name) { +static Ast* __scope_get_resolved_variable(Scope *self, Scope *start, AstCompilerContext *context, BufferView name) { Ast *result; bool exists; Scope *prev_scope; @@ -215,16 +247,16 @@ static Ast* scope_get_resolved_variable(Scope *self, AstCompilerContext *context if(!exists) { Parser *parser; if(self->parent) - return scope_get_resolved_variable(self->parent, context, name); + return __scope_get_resolved_variable(self->parent, start, context, name); - parser = scope_get_parser(self); + parser = scope_get_parser(start); parser_print_error(parser, name.data, "Undefined reference to variable \"%.*s\"", name.size, name.data); throw(AST_ERR); } /* Need to change scope here because we are changing the visible scope - and the ast object may be in another scope than the current + and the ast object may be in another scope than the current resolving ast. */ prev_scope = context->scope; @@ -236,9 +268,58 @@ static Ast* scope_get_resolved_variable(Scope *self, AstCompilerContext *context return result; } +static Ast* scope_get_resolved_variable(Scope *self, AstCompilerContext *context, BufferView name) { + return __scope_get_resolved_variable(self, self, context, name); +} + +static void function_signature_resolve(FunctionSignature *self, AstCompilerContext *context) { + /* TODO: Implement */ + (void)self; + (void)context; +} + static void variable_resolve(Variable *self, AstCompilerContext *context, AstResolveData *resolve_data) { - if(!resolve_data->type) - resolve_data->type = scope_get_resolved_variable(context->scope, context, self->name)->resolve_data.type; + if(!self->resolved_var) { + self->resolved_var = scope_get_resolved_variable(context->scope, context, self->name); + resolve_data->type = self->resolved_var->resolve_data.type; + } +} + +static LhsExpr* variable_get_resolved_type(Variable *self) { + assert(self->resolved_var); + return self->resolved_var->value.lhs_expr; +} + +static void variable_type_resolve(VariableType *self, AstCompilerContext *context, AstResolveData *resolve_data) { + switch(self->type) { + case VARIABLE_TYPE_NONE: + return; + case VARIABLE_TYPE_VARIABLE: + variable_resolve(self->value.variable, context, resolve_data); + break; + case VARIABLE_TYPE_SIGNATURE: + function_signature_resolve(self->value.signature, context); + break; + } +} + +static BufferView variable_type_get_name(VariableType *self) { + BufferView result; + switch(self->type) { + case VARIABLE_TYPE_NONE: + result.data = NULL; + result.size = 0; + break; + case VARIABLE_TYPE_VARIABLE: + result = self->value.variable->name; + break; + case VARIABLE_TYPE_SIGNATURE: + /* TODO: Save reference to signature in code in the FunctionSignature type */ + result.data = "fn()"; + result.size = 4; + break; + } + return result; } static LhsExpr* lhsexpr_resolve_rhs(LhsExpr *self, AstCompilerContext *context) { @@ -259,8 +340,7 @@ static void lhsexpr_resolve(Ast *ast, AstCompilerContext *context) { self = ast->value.lhs_expr; rhs_resolve_type = NULL; - if(self->type.name.data) - variable_resolve(&self->type, context, &ast->resolve_data); + variable_type_resolve(&self->type, context, &ast->resolve_data); /* TODO: When parameters and return types are implemented, AST_RESOLVE_END should be set after @@ -274,10 +354,15 @@ static void lhsexpr_resolve(Ast *ast, AstCompilerContext *context) { rhs_resolve_type = lhsexpr_resolve_rhs(self, context); /* self->rhs_expr can be null here because this is valid: var num: i32; */ + /* TODO: Add casting */ if(ast->resolve_data.type && self->rhs_expr && ast->resolve_data.type != rhs_resolve_type) { Parser *parser; parser = scope_get_parser(context->scope); - parser_print_error(parser, self->type.name.data, + /* + TODO: Instead of using self->var_name, using type name. This cant be done right now because + type can be function signature. + */ + parser_print_error(parser, self->var_name.data, "Variable type and variable assignment type (right-hand side) do not match"); throw(AST_ERR); } @@ -287,6 +372,67 @@ static void lhsexpr_resolve(Ast *ast, AstCompilerContext *context) { ast->resolve_data.type = rhs_resolve_type; } +static LhsExpr* binop_get_lhs_expr(Binop *self) { + if(self->rhs) { + if(self->rhs->type == AST_LHS) + return self->rhs->value.lhs_expr; + else if(self->rhs->type == AST_BINOP) + return binop_get_lhs_expr(self->rhs->value.binop); + } else { + if(self->lhs->type == AST_LHS) + return self->lhs->value.lhs_expr; + else if(self->lhs->type == AST_BINOP) + return binop_get_lhs_expr(self->lhs->value.binop); + } + return NULL; +} + +static void assignmentexpr_resolve(Ast *ast, AstCompilerContext *context) { + AssignmentExpr *self; + LhsExpr *lhs_source; + + assert(ast->type == AST_ASSIGN); + self = ast->value.assign_expr; + lhs_source = NULL; + + ast_resolve(self->lhs_expr, context); + ast_resolve(self->rhs_expr, context); + + if(self->lhs_expr->type == AST_VARIABLE) + lhs_source = variable_get_resolved_type(self->lhs_expr->value.variable); + else if(self->lhs_expr->type == AST_BINOP) + lhs_source = binop_get_lhs_expr(self->lhs_expr->value.binop); + + /* This also covers extern variables, since extern variables are always const */ + /* TODO: var.field type expressions should also be checked */ + if(lhs_source && lhs_source->is_const) { + Parser *parser; + parser = scope_get_parser(context->scope); + parser_print_error(parser, ast_get_code_reference(self->lhs_expr).data, "Can't assign to a const value"); + throw(AST_ERR); + } + + /* TODO: Add casting */ + if(self->lhs_expr->resolve_data.type != self->rhs_expr->resolve_data.type) { + Parser *parser; + BufferView rhs_type_name; + BufferView lhs_type_name; + + parser = scope_get_parser(context->scope); + rhs_type_name = variable_type_get_name(&self->rhs_expr->resolve_data.type->type); + lhs_type_name = variable_type_get_name(&self->lhs_expr->resolve_data.type->type); + /* + TODO: Instead of using self->var_name, using type name. This cant be done right now because + type can be function signature. + */ + parser_print_error(parser, ast_get_code_reference(self->lhs_expr).data, + "Can't cast data of type %.*s to type %.*s", rhs_type_name.size, rhs_type_name.data, lhs_type_name.size, lhs_type_name.data); + throw(AST_ERR); + } + + ast->resolve_data.type = self->lhs_expr->resolve_data.type; +} + static void import_resolve(Ast *ast, AstCompilerContext *context) { Import *self; assert(ast->type == AST_IMPORT); @@ -331,6 +477,7 @@ static void funccall_resolve(Ast *self, AstCompilerContext *context) { func_call = self->value.func_call; variable_resolve(&func_call->func, context, &self->resolve_data); + /* Attemping to use call syntax (variable_name ( ) ) with a variable that is not a function */ if(self->resolve_data.type->rhs_expr->type != AST_FUNCTION_DECL) { Parser *caller_parser; Parser *callee_parser; @@ -420,8 +567,12 @@ static void binop_resolve(Ast *ast, AstCompilerContext *context) { if(self->type == BINOP_DOT && (self->rhs->type == AST_VARIABLE || self->rhs->type == AST_FUNCTION_CALL)) { binop_resolve_dot_access(ast, context); /* Only function call has extra data that needs to be resolved (args) */ - if(self->rhs->type == AST_FUNCTION_CALL) + if(self->rhs->type == AST_FUNCTION_CALL) { + Scope *prev_scope = context->scope; + context->scope = lhsexpr_get_scope(self->lhs->resolve_data.type); ast_resolve(self->rhs, context); + context->scope = prev_scope; + } self->rhs->resolve_data.status = AST_RESOLVED; ast->resolve_data.type = self->rhs->resolve_data.type; } else { @@ -452,6 +603,28 @@ static void binop_resolve(Ast *ast, AstCompilerContext *context) { } } +static void else_if_statement_resolve(ElseIfStatement *else_if_stmt, AstCompilerContext *context) { + if(else_if_stmt->condition) + ast_resolve(else_if_stmt->condition, context); + + scope_resolve(&else_if_stmt->body, context); + if(else_if_stmt->next_else_if_stmt) + else_if_statement_resolve(else_if_stmt->next_else_if_stmt, context); +} + +static void if_statement_resolve(IfStatement *if_stmt, AstCompilerContext *context) { + assert(if_stmt->condition); + ast_resolve(if_stmt->condition, context); + scope_resolve(&if_stmt->body, context); + if(if_stmt->else_if_stmt) + else_if_statement_resolve(if_stmt->else_if_stmt, context); +} + +static void while_statement_resolve(WhileStatement *while_stmt, AstCompilerContext *context) { + ast_resolve(while_stmt->condition, context); + scope_resolve(&while_stmt->body, context); +} + void ast_resolve(Ast *self, AstCompilerContext *context) { assert(self); /* @@ -515,6 +688,9 @@ void ast_resolve(Ast *self, AstCompilerContext *context) { case AST_LHS: lhsexpr_resolve(self, context); break; + case AST_ASSIGN: + assignmentexpr_resolve(self, context); + break; case AST_IMPORT: /* TODO: When @import(...).data syntax is added, implement the resolve for it */ import_resolve(self, context); @@ -529,6 +705,12 @@ void ast_resolve(Ast *self, AstCompilerContext *context) { case AST_BINOP: binop_resolve(self, context); break; + case AST_IF_STATEMENT: + if_statement_resolve(self->value.if_stmt, context); + break; + case AST_WHILE_STATEMENT: + while_statement_resolve(self->value.while_stmt, context); + break; } /* TODO: See comment at the top of this function */ self->resolve_data.status = AST_RESOLVED; diff --git a/src/bytecode/bytecode.c b/src/bytecode/bytecode.c index 6774a54..789a9f6 100644 --- a/src/bytecode/bytecode.c +++ b/src/bytecode/bytecode.c @@ -46,6 +46,17 @@ static CHECK_RESULT usize ssa_extract_func_call(u8 *instruction_data, SsaInsFunc return sizeof(result->result) + sizeof(result->func_decl); } +static CHECK_RESULT usize ssa_extract_jump_zero(u8 *instruction_data, SsaInsJumpZero *result) { + am_memcpy(&result->condition_reg, instruction_data, sizeof(result->condition_reg)); + am_memcpy(&result->jump_offset, instruction_data + sizeof(result->condition_reg), sizeof(result->jump_offset)); + return sizeof(result->condition_reg) + sizeof(result->jump_offset); +} + +static CHECK_RESULT usize ssa_extract_jump(u8 *instruction_data, SsaInsJump *result) { + am_memcpy(&result->jump_offset, instruction_data, sizeof(result->jump_offset)); + return sizeof(result->jump_offset); +} + static void add_intermediates(BytecodeCompilerContext *self) { Ssa *ssa; Buffer *instructions; @@ -66,19 +77,44 @@ static void add_intermediates(BytecodeCompilerContext *self) { } } +#if 0 #define NUM_MAX_REGS 256 +#define NUM_MAX_FUNC_ARGS 32 + +static const char* lhs_expr_get_c_name(BytecodeCompilerContext *self, LhsExpr *lhs_expr) { + if(lhs_expr == self->parser->compiler->default_types.i64) { + return "i64"; + } else if(lhs_expr == self->parser->compiler->default_types.f64) { + return "f64"; + } else if(lhs_expr == self->parser->compiler->default_types.str) { + return"const char*"; + } else { + amal_log_error("Invalid rhs type %p", lhs_expr); + assert(bool_false && "TODO: Implement"); + return ""; + } +} +#endif static void add_instructions(BytecodeCompilerContext *self) { Ssa *ssa; u8 *instruction; u8 *instructions_end; + SsaInsForm1 ssa_ins_form1; SsaInsForm2 ssa_ins_form2; SsaInsFuncStart ssa_ins_func_start; SsaInsFuncCall ssa_ins_func_call; + SsaInsJumpZero ssa_ins_jump_zero; + SsaInsJump ssa_ins_jump; + FILE *file; char *filename; + #ifdef COMPILE_TO_C LhsExpr *reg_types[NUM_MAX_REGS]; /* TODO: Remove this. Encode this data in the register itself */ + SsaRegister func_arg_stack[NUM_MAX_FUNC_ARGS]; /* TODO: Remove this? */ + int func_arg_index; + #endif ssa = self->parser->ssa; instruction = buffer_begin(&ssa->instructions); @@ -90,15 +126,27 @@ static void add_instructions(BytecodeCompilerContext *self) { strcat(filename, ".z"); file = fopen(filename, "wb"); free(filename); + #ifdef COMPILE_TO_C #ifdef DEBUG am_memset(reg_types, 0, sizeof(reg_types)); #endif + func_arg_index = 0; fputs("typedef i64 signed long long;\n", file); fputs("typedef f64 double;\n", file); + #define ARITH_OP(op) do {\ + const char *rhs_type_name; \ + instruction += ssa_extract_form2(instruction, &ssa_ins_form2); \ + assert(ssa_ins_form2.result < NUM_MAX_REGS); \ + assert(ssa_ins_form2.lhs < NUM_MAX_REGS); \ + rhs_type_name = lhs_expr_get_c_name(self, reg_types[ssa_ins_form2.lhs]); \ + fprintf(file, "%s r%d = r%d %s r%d;\n", rhs_type_name, ssa_ins_form2.result, ssa_ins_form2.lhs, (op), ssa_ins_form2.rhs); \ + reg_types[ssa_ins_form2.result] = reg_types[ssa_ins_form2.lhs]; \ + } while(0) + while(instruction != instructions_end) { - switch(*instruction++) { + switch((SsaInstruction)*instruction++) { case SSA_ASSIGN_INTER: { SsaNumber number; instruction += ssa_extract_form1(instruction, &ssa_ins_form1); @@ -125,54 +173,32 @@ static void add_instructions(BytecodeCompilerContext *self) { break; } case SSA_ASSIGN_REG: { - LhsExpr *rhs_type; const char *rhs_type_name; instruction += ssa_extract_form1(instruction, &ssa_ins_form1); assert(ssa_ins_form1.rhs < NUM_MAX_REGS); - rhs_type = reg_types[ssa_ins_form1.rhs]; - if(rhs_type == self->parser->compiler->default_types.i64) { - rhs_type_name = "i64"; - } else if(rhs_type == self->parser->compiler->default_types.f64) { - rhs_type_name = "f64"; - } else if(rhs_type == self->parser->compiler->default_types.str) { - rhs_type_name = "const char*"; - } else { - amal_log_error("Invalid rhs type %p for reg %d", rhs_type, ssa_ins_form1.rhs); - assert(bool_false && "TODO: Implement"); - } + rhs_type_name = lhs_expr_get_c_name(self, reg_types[ssa_ins_form1.rhs]); fprintf(file, "%s r%d = r%d;\n", rhs_type_name, ssa_ins_form1.lhs, ssa_ins_form1.rhs); + reg_types[ssa_ins_form1.lhs] = reg_types[ssa_ins_form1.rhs]; break; } case SSA_ADD: { - instruction += ssa_extract_form2(instruction, &ssa_ins_form2); - fprintf(file, "r%d = r%d + r%d;\n", ssa_ins_form2.result, ssa_ins_form2.lhs, ssa_ins_form2.rhs); - assert(ssa_ins_form2.result < NUM_MAX_REGS); - assert(ssa_ins_form2.rhs < NUM_MAX_REGS); - reg_types[ssa_ins_form2.result] = reg_types[ssa_ins_form2.rhs]; + ARITH_OP("+"); break; } case SSA_SUB: { - instruction += ssa_extract_form2(instruction, &ssa_ins_form2); - fprintf(file, "r%d = r%d - r%d;\n", ssa_ins_form2.result, ssa_ins_form2.lhs, ssa_ins_form2.rhs); - assert(ssa_ins_form2.result < NUM_MAX_REGS); - assert(ssa_ins_form2.rhs < NUM_MAX_REGS); - reg_types[ssa_ins_form2.result] = reg_types[ssa_ins_form2.rhs]; + ARITH_OP("-"); break; } case SSA_MUL: { - instruction += ssa_extract_form2(instruction, &ssa_ins_form2); - fprintf(file, "r%d = r%d * r%d;\n", ssa_ins_form2.result, ssa_ins_form2.lhs, ssa_ins_form2.rhs); - assert(ssa_ins_form2.result < NUM_MAX_REGS); - assert(ssa_ins_form2.rhs < NUM_MAX_REGS); - reg_types[ssa_ins_form2.result] = reg_types[ssa_ins_form2.rhs]; + ARITH_OP("*"); break; } case SSA_DIV: { - instruction += ssa_extract_form2(instruction, &ssa_ins_form2); - fprintf(file, "r%d = r%d / r%d;\n", ssa_ins_form2.result, ssa_ins_form2.lhs, ssa_ins_form2.rhs); - assert(ssa_ins_form2.result < NUM_MAX_REGS); - assert(ssa_ins_form2.rhs < NUM_MAX_REGS); - reg_types[ssa_ins_form2.result] = reg_types[ssa_ins_form2.rhs]; + ARITH_OP("/"); + break; + } + case SSA_EQUALS: { + ARITH_OP("=="); break; } case SSA_FUNC_START: { @@ -194,20 +220,134 @@ static void add_instructions(BytecodeCompilerContext *self) { SsaRegister reg; am_memcpy(®, instruction, sizeof(SsaRegister)); instruction += sizeof(SsaRegister); - fprintf(file, "PUSH r%d ***\n", reg); + assert(func_arg_index < NUM_MAX_FUNC_ARGS); + func_arg_stack[func_arg_index++] = reg; break; } - case SSA_CALL: + case SSA_CALL: { + int i; instruction += ssa_extract_func_call(instruction, &ssa_ins_func_call); - fprintf(file, "r%d = CALL %p ***\n", ssa_ins_func_call.result, ssa_ins_func_call.func_decl); + fprintf(file, "r%d = f%p(", ssa_ins_func_call.result, ssa_ins_func_call.func_decl); + for(i = 0; i < func_arg_index; ++i) { + if(i > 0) + fputs(", ", file); + fprintf(file, "r%d", func_arg_stack[i]); + } + func_arg_index = 0; + fputs(");\n", file); + break; + } + case SSA_JUMP_ZERO: { + assert(bool_false && "Not implemented!"); + instruction += ssa_extract_jump_zero(instruction, &ssa_ins_jump_zero); + break; + } + case SSA_JUMP: { + assert(bool_false && "Not implemented!"); + instruction += ssa_extract_jump(instruction, &ssa_ins_jump); break; + } } } + #else + #define ARITH_OP(op) do {\ + instruction += ssa_extract_form2(instruction, &ssa_ins_form2); \ + fprintf(stderr, "%s r%d, r%d, r%d\n", (op), ssa_ins_form2.result, ssa_ins_form2.lhs, ssa_ins_form2.rhs); \ + } while(0) + while(instruction != instructions_end) { + SsaInstruction ins = (SsaInstruction)*instruction++; + switch(ins) { + case SSA_ASSIGN_INTER: { + instruction += ssa_extract_form1(instruction, &ssa_ins_form1); + fprintf(file, "mov r%d, i%d\n", ssa_ins_form1.lhs, ssa_ins_form1.rhs); + break; + } + case SSA_ASSIGN_STRING: { + instruction += ssa_extract_form1(instruction, &ssa_ins_form1); + fprintf(file, "mov r%d, s%d\n", ssa_ins_form1.lhs, ssa_ins_form1.rhs); + break; + } + case SSA_ASSIGN_REG: { + instruction += ssa_extract_form1(instruction, &ssa_ins_form1); + fprintf(file, "mov r%d, r%d\n", ssa_ins_form1.lhs, ssa_ins_form1.rhs); + break; + } + case SSA_ADD: { + ARITH_OP("add"); + break; + } + case SSA_SUB: { + ARITH_OP("sub"); + break; + } + case SSA_MUL: { + ARITH_OP("mul"); + break; + } + case SSA_DIV: { + ARITH_OP("div"); + break; + } + case SSA_EQUALS: { + ARITH_OP("eq"); + break; + } + case SSA_FUNC_START: { + instruction += ssa_extract_func_start(instruction, &ssa_ins_func_start); + fprintf(file, "FUNC_START %d\n", ssa_ins_func_start.num_args); + break; + } + case SSA_FUNC_END: { + fprintf(file, "FUNC_END\n"); + break; + } + case SSA_PUSH: { + SsaRegister reg; + am_memcpy(®, instruction, sizeof(SsaRegister)); + instruction += sizeof(SsaRegister); + fprintf(file, "push r%d\n", reg); + break; + } + case SSA_CALL: { + /* TODO: Add args, using number of bytes to pop after function call. */ + /* + TODO: Pass return register to function. The register should be a pointer that + has the size of the function return values so the return values can fit in it. + */ + /* + TODO: Using ssa_func_index only works correctly if the function was defined in the same + file as the function call. To make this work with calling functions in other files, + ssa_func_index should also have an offset index or something like that. + So each file has it's own function list with indices and when they need to be combined in the end, + the function indices can be increased by their block index (ssa_func_index + block index), where block index + is defined as the size of all previous files' number of functions. + */ + instruction += ssa_extract_func_call(instruction, &ssa_ins_func_call); + fprintf(file, "call %d\n", ssa_ins_func_call.func_decl->ssa_func_index); + break; + } + case SSA_JUMP_ZERO: { + instruction += ssa_extract_jump_zero(instruction, &ssa_ins_jump_zero); + fprintf(file, "jz r%d, %d\n", ssa_ins_jump_zero.condition_reg, ssa_ins_jump_zero.jump_offset); + break; + } + case SSA_JUMP: { + instruction += ssa_extract_jump(instruction, &ssa_ins_jump); + fprintf(file, "jmp %d\n", ssa_ins_jump.jump_offset); + break; + } + default: + amal_log_error("Instruction not yet implemented: %d", ins); + assert(bool_false && "Instruction not yet implemented"); + } + } + #endif /* COMPILE_TO_C */ fclose(file); } void generate_bytecode_from_ssa(BytecodeCompilerContext *self) { add_intermediates(self); + /* TODO: Also add strings in ssa, so we can index them */ add_instructions(self); } diff --git a/src/compiler.c b/src/compiler.c index 30a583a..bdcb1c8 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -40,7 +40,7 @@ static CHECK_RESULT int create_default_type(amal_compiler *compiler, const char return_if_error(structdecl_init(struct_decl, &compiler->root_scope, &compiler->allocator)); return_if_error(scoped_allocator_alloc(&compiler->allocator, sizeof(LhsExpr), (void**)lhs_expr)); - return_if_error(lhsexpr_init(*lhs_expr, bool_true, bool_true, + return_if_error(lhsexpr_init(*lhs_expr, bool_true, bool_true, bool_true, create_buffer_view(name, strnlen(name, PATH_MAX)), &compiler->allocator)); return_if_error(ast_create(&compiler->allocator, struct_decl, AST_STRUCT_DECL, &(*lhs_expr)->rhs_expr)); @@ -242,6 +242,7 @@ static CHECK_RESULT int thread_generate_ssa(Parser *parser) { return_if_error(scoped_allocator_alloc(parser->allocator, sizeof(Ssa), (void**)&compiler_context.ssa)); return_if_error(ssa_init(compiler_context.ssa, parser->allocator)); + compiler_context.compiler = parser->compiler; parser->ssa = compiler_context.ssa; amal_log_debug("Generating SSA for file: %.*s", parser->tokenizer.code_name.size, parser->tokenizer.code_name.data); result = setjmp(compiler_context.env); diff --git a/src/parser.c b/src/parser.c index f05b31d..c0d2c6d 100644 --- a/src/parser.c +++ b/src/parser.c @@ -20,9 +20,10 @@ do { \ throw(return_if_result); \ } while(0) -static CHECK_RESULT Ast* parser_parse_rhs_start(Parser *self, bool is_standalone); +static CHECK_RESULT Ast* parser_parse_rhs(Parser *self); static CHECK_RESULT Ast* parser_parse_body(Parser *self); static CHECK_RESULT Ast* parser_parse_struct_body(Parser *self); +static CHECK_RESULT Ast* parser_parse_rhs_binop(Parser *self); static void parser_queue_file(Parser *self, BufferView path, FileScopeReference **file_scope); int parser_thread_data_init(ParserThreadData *self) { @@ -61,7 +62,7 @@ int parser_init(Parser *self, amal_compiler *compiler, ScopedAllocator *allocato self->error_context = ERROR_CONTEXT_NONE; return_if_error(structdecl_init(&self->struct_decl, &compiler->root_scope, allocator)); self->struct_decl.body.parser = self; - return_if_error(lhsexpr_init(&self->file_decl, bool_true, bool_true, create_buffer_view_null(), self->allocator)); + return_if_error(lhsexpr_init(&self->file_decl, bool_true, bool_true, bool_true, create_buffer_view_null(), self->allocator)); return_if_error(ast_create(self->allocator, &self->struct_decl, AST_STRUCT_DECL, &self->file_decl.rhs_expr)); self->current_scope = &self->struct_decl.body; self->has_func_parent = bool_false; @@ -132,30 +133,78 @@ static void parser_parse_struct_body_loop(Parser *self, Scope *scope) { } /* -VAR_TYPE_DEF = ':' TOK_IDENTIFIER +TODO: Implement params and return types +FUNC_SIGNATURE = 'fn' ('(' ')')? */ -static void parser_parse_var_type_def(Parser *self, BufferView *type_name) { +static CHECK_RESULT FunctionSignature* parser_parse_function_signature(Parser *self) { + FunctionSignature *signature; bool match; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_FN, &match)); + if(!match) + return NULL; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_PAREN, &match)); + if(match) { + /* TODO: Parse parameters */ + throw_if_error(tokenizer_accept(&self->tokenizer, TOK_CLOSING_PAREN)); + /* TODO: Parse return types */ + } + + throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(FunctionSignature), (void**)&signature)); + signature->params = 0; + return signature; +} + +/* +VAR_TYPE_DEF = ':' TOK_IDENTIFIER|FUNC_SIGNATURE +*/ +static CHECK_RESULT int parser_parse_var_type_def(Parser *self, VariableType *result) { + bool match; + + result->type = VARIABLE_TYPE_NONE; + result->value.variable = NULL; + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_COLON, &match)); + if(!match) + return -1; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IDENTIFIER, &match)); if(match) { - throw_if_error(tokenizer_accept(&self->tokenizer, TOK_IDENTIFIER)); - *type_name = self->tokenizer.value.identifier; + result->type = VARIABLE_TYPE_VARIABLE; + throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(Variable), (void**)&result->value.variable)); + variable_init(result->value.variable, self->tokenizer.value.identifier); + return 0; + } + + result->type = VARIABLE_TYPE_SIGNATURE; + result->value.signature = parser_parse_function_signature(self); + if(!result->value.signature) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), + "Expected type or closure signature"); + throw(PARSER_UNEXPECTED_TOKEN); } + return 0; } /* -LHS = ('pub'? 'const' TOK_IDENTIFIER VAR_TYPE_DEF? '=') | - ('pub'? 'var' TOK_IDENTIFIER VAR_TYPE_DEF? '='|';') +LHS_DECLARATION = 'extern'? 'pub'? 'const'|'var' TOK_IDENTIFIER VAR_TYPE_DEF? */ -static CHECK_RESULT LhsExpr* parser_parse_lhs(Parser *self, bool *assignment_or_rhs) { +static CHECK_RESULT LhsExpr* parser_parse_declaration_lhs(Parser *self) { LhsExpr *result; + bool is_extern; bool is_pub; bool is_const; - bool match; BufferView var_name; - result = NULL; - *assignment_or_rhs = bool_true; + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_EXTERN, &is_extern)); + if(is_extern && self->has_func_parent) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), + "Only declarations in structs can be extern"); + throw(PARSER_UNEXPECTED_TOKEN); + } throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_PUB, &is_pub)); if(is_pub && self->has_func_parent) { @@ -168,77 +217,48 @@ static CHECK_RESULT LhsExpr* parser_parse_lhs(Parser *self, bool *assignment_or_ throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_CONST, &is_const)); if(!is_const) { bool isVar; + + if(is_extern) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), + "Extern variable have to be declared with \"const\""); + throw(PARSER_UNEXPECTED_TOKEN); + } + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_VAR, &isVar)); if(!isVar) - return result; + return NULL; } throw_if_error(tokenizer_accept(&self->tokenizer, TOK_IDENTIFIER)); var_name = self->tokenizer.value.identifier; throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(LhsExpr), (void**)&result)); - throw_if_error(lhsexpr_init(result, is_pub, is_const, var_name, self->allocator)); - - parser_parse_var_type_def(self, &result->type.name); - - throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_EQUALS, &match)); - if(match) { - *assignment_or_rhs = bool_true; - return result; - } + throw_if_error(lhsexpr_init(result, is_extern, is_pub, is_const, var_name, self->allocator)); - if(!result->type.name.data) { - self->error = tokenizer_create_error(&self->tokenizer, - tokenizer_get_error_index(&self->tokenizer), - "Variable declaration requires type or right-hand side expression (Expected ':' or '=')"); - throw(PARSER_UNEXPECTED_TOKEN); - } - - *assignment_or_rhs = bool_false; - throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_SEMICOLON, &match)); - if(match && is_const) { - self->error = tokenizer_create_error(&self->tokenizer, - tokenizer_get_error_index(&self->tokenizer), - "Const variable declaration requires assignment (expected '=', got ';')"); - throw(PARSER_UNEXPECTED_TOKEN); - } - if(!match) { - self->error = tokenizer_create_error(&self->tokenizer, - tokenizer_get_error_index(&self->tokenizer), - "Expected ';'"); - throw(PARSER_UNEXPECTED_TOKEN); - } + ignore_result_int(parser_parse_var_type_def(self, &result->type)); return result; } /* -TODO: Implement params and return types -CLOSURE = 'fn' ('(' PARAM? (',' PARAM)* ')')? '{' BODY_LOOP '}' + +CLOSURE = FUNC_SIGNATURE '{' BODY_LOOP '}' */ static CHECK_RESULT FunctionDecl* parser_parse_closure(Parser *self) { + FunctionSignature *signature; FunctionDecl *result; - bool match; bool prev_has_func_parent; - result = NULL; - throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_FN, &match)); - if(!match) - return result; - - throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_BRACE, &match)); - if(!match) { - throw_if_error(tokenizer_accept(&self->tokenizer, TOK_OPEN_PAREN)); - /* TODO: Parse parameters */ - throw_if_error(tokenizer_accept(&self->tokenizer, TOK_CLOSING_PAREN)); - /* TODO: Parse return types */ - throw_if_error(tokenizer_accept(&self->tokenizer, TOK_OPEN_BRACE)); - } + signature = parser_parse_function_signature(self); + if(!signature) + return NULL; throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(FunctionDecl), (void**)&result)); - throw_if_error(funcdecl_init(result, self->current_scope, self->allocator)); + throw_if_error(funcdecl_init(result, signature, self->current_scope, self->allocator)); self->current_scope = &result->body; prev_has_func_parent = self->has_func_parent; self->has_func_parent = bool_true; + throw_if_error(tokenizer_accept(&self->tokenizer, TOK_OPEN_BRACE)); parser_parse_body_loop(self, self->current_scope, TOK_CLOSING_BRACE); self->current_scope = result->body.parent; self->has_func_parent = prev_has_func_parent; @@ -286,14 +306,14 @@ static void parser_parse_function_args(Parser *self, FunctionCall *func_call) { throw_if_error(tokenizer_accept(&self->tokenizer, TOK_COMMA)); first_arg = bool_false; - arg_expr = parser_parse_rhs_start(self, bool_false); + arg_expr = parser_parse_rhs(self); throw_if_error(buffer_append(&func_call->args, &arg_expr, sizeof(arg_expr))); } } /* VARIABLE = TOK_IDENTIFIER -FUNC_CALL_OR_VARIABLE = VARIABLE '(' FUNC_ARGS ')' +FUNC_CALL_OR_VARIABLE = VARIABLE ('(' FUNC_ARGS ')')? */ static CHECK_RESULT Ast* parser_parse_function_call_or_variable(Parser *self) { Ast *result; @@ -319,6 +339,7 @@ static CHECK_RESULT Ast* parser_parse_function_call_or_variable(Parser *self) { throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(FunctionCall), (void**)&func_call)); throw_if_error(funccall_init(func_call, self->tokenizer.value.identifier, self->allocator)); throw_if_error(ast_create(self->allocator, func_call, AST_FUNCTION_CALL, &result)); + /* Ends after TOK_CLOSING_PAREN */ parser_parse_function_args(self, func_call); return result; } @@ -340,6 +361,109 @@ static CHECK_RESULT Import* parser_parse_import(Parser *self) { return result; } +/* +ELSE_IF_STATEMENT = 'else' ('if' RHS_BINOP)? ('{' BODY_LOOP '}')|BODY +*/ +static CHECK_RESULT ElseIfStatement* parser_parse_else_if_statement(Parser *self) { + ElseIfStatement *result; + bool match; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_ELSE, &match)); + if(!match) + return NULL; + + throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(ElseIfStatement), (void**)&result)); + throw_if_error(else_if_statement_init(result, self->current_scope, self->allocator)); + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IF, &match)); + if(match) + result->condition = parser_parse_rhs_binop(self); + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_BRACE, &match)); + if(match) { + parser_parse_body_loop(self, &result->body, TOK_CLOSING_BRACE); + } else { + Ast *body_obj; + body_obj = parser_parse_body(self); + throw_if_error(scope_add_child(&result->body, body_obj)); + } + + return result; +} + +static void parser_parse_else_if_statement_loop(Parser *self, IfStatement *if_stmt) { + ElseIfStatement *else_if_stmt; + else_if_stmt = if_stmt->else_if_stmt; + for(;;) { + ElseIfStatement *next_else_if; + next_else_if = parser_parse_else_if_statement(self); + if(!next_else_if) + break; + else_if_stmt->next_else_if_stmt = next_else_if; + /* else statement, which because they have no conditions; can't be followed by another else statement */ + if(!else_if_stmt->condition) + break; + else_if_stmt = next_else_if; + } +} + +/* +IF_STATEMENT = 'if' RHS_BINOP ('{' BODY_LOOP '}')|BODY ELSE_IF_STATEMENT* +*/ +static CHECK_RESULT IfStatement* parser_parse_if_statement(Parser *self) { + IfStatement *result; + bool match; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IF, &match)); + if(!match) + return NULL; + + throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(IfStatement), (void**)&result)); + throw_if_error(if_statement_init(result, self->current_scope, self->allocator)); + + result->condition = parser_parse_rhs_binop(self); + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_BRACE, &match)); + if(match) { + parser_parse_body_loop(self, &result->body, TOK_CLOSING_BRACE); + } else { + Ast *body_obj; + body_obj = parser_parse_body(self); + throw_if_error(scope_add_child(&result->body, body_obj)); + } + + parser_parse_else_if_statement_loop(self, result); + return result; +} + +/* +WHILE_STATEMENT = 'while' RHS_BINOP ('{' BODY_LOOP '}')|BODY +*/ +static CHECK_RESULT WhileStatement* parser_parse_while_statement(Parser *self) { + WhileStatement *result; + bool match; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_WHILE, &match)); + if(!match) + return NULL; + + throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(WhileStatement), (void**)&result)); + throw_if_error(while_statement_init(result, self->current_scope, self->allocator)); + + result->condition = parser_parse_rhs_binop(self); + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_BRACE, &match)); + if(match) { + parser_parse_body_loop(self, &result->body, TOK_CLOSING_BRACE); + } else { + Ast *body_obj; + body_obj = parser_parse_body(self); + throw_if_error(scope_add_child(&result->body, body_obj)); + } + + return result; +} + static CHECK_RESULT Ast* parser_parse_number(Parser *self) { Ast *result; bool match; @@ -388,8 +512,6 @@ static Ast* parser_parse_rhs_single_expr(Parser *self) { throw(PARSER_UNEXPECTED_TOKEN); } -static CHECK_RESULT Ast* parser_parse_rhs_binop(Parser *self); - /* RHS_BINOP_OPT_PAREN = RHS_S | '(' RHS_BINOP ')' */ @@ -442,7 +564,7 @@ RHS = RHS_BINOP ';' Note: Parantheses count has to match for the beginning paranthesis and the ending parenthesis. */ -static CHECK_RESULT Ast* parser_parse_rhs(Parser *self) { +Ast* parser_parse_rhs(Parser *self) { /* TODO: If binop only contains one expression, then use that directly for @rhs_expr */ Ast *result; result = parser_parse_rhs_binop(self); @@ -452,41 +574,9 @@ static CHECK_RESULT Ast* parser_parse_rhs(Parser *self) { return result; } -/* -RHS_START = CLOSURE | IMPORT | RHS -*/ -Ast* parser_parse_rhs_start(Parser *self, bool is_standalone) { - Ast *result; - - if(!is_standalone) { - FunctionDecl *func_decl; - StructDecl *struct_decl; - Import *import; - - func_decl = parser_parse_closure(self); - if(func_decl) { - throw_if_error(ast_create(self->allocator, func_decl, AST_FUNCTION_DECL, &result)); - return result; - } - - struct_decl = parser_parse_struct_decl(self); - if(struct_decl) { - throw_if_error(ast_create(self->allocator, struct_decl, AST_STRUCT_DECL, &result)); - return result; - } - - import = parser_parse_import(self); - if(import) { - parser_queue_file(self, import->path, &import->file_scope); - throw_if_error(ast_create(self->allocator, import, AST_IMPORT, &result)); - return result; - } - self->error_context = ERROR_CONTEXT_RHS_STANDALONE; - } - - result = parser_parse_rhs(self); - self->error_context = ERROR_CONTEXT_NONE; - return result; +static bool type_requires_semicolon_at_end(AstType type) { + /* TODO: Check for tables */ + return type != AST_FUNCTION_DECL && type != AST_STRUCT_DECL && type != AST_IF_STATEMENT; } /* @@ -508,37 +598,109 @@ static void parser_parse_body_semicolon(Parser *self, Ast *expr) { return; } - /* TODO: Check for tables */ - if(expr->type != AST_FUNCTION_DECL && expr->type != AST_STRUCT_DECL) + if(type_requires_semicolon_at_end(expr->type)) throw_if_error(tokenizer_accept(&self->tokenizer, TOK_SEMICOLON)); } +/* CONDITIONAL = (IF_STATEMENT|WHILE_STATEMENT)? */ +static CHECK_RESULT Ast* parser_parse_conditional(Parser *self) { + Ast *result; + IfStatement *if_stmt; + WhileStatement *while_stmt; + + if_stmt = parser_parse_if_statement(self); + if(if_stmt) { + throw_if_error(ast_create(self->allocator, if_stmt, AST_IF_STATEMENT, &result)); + self->error_context = ERROR_CONTEXT_NONE; + return result; + } + + while_stmt = parser_parse_while_statement(self); + if(while_stmt) { + throw_if_error(ast_create(self->allocator, while_stmt, AST_WHILE_STATEMENT, &result)); + self->error_context = ERROR_CONTEXT_NONE; + return result; + } + + return NULL; +} + /* -BODY = LHS ';' | - (LHS '=' RHS_START BODY_SEMICOLON) | - (RHS_START BODY_SEMICOLON) +BODY = (LHS_DECLARATION ';') | + (LHS_DECLARATION '=' CLOSURE|STRUCT|IMPORT|(RHS BODY_SEMICOLON)) | + CONDITIONAL | + (RHS ';'|('=' RHS BODY_SEMICOLON)) */ Ast* parser_parse_body(Parser *self) { - bool assignment_or_rhs; Ast *result; LhsExpr *lhs_expr; Ast *rhs_expr; - lhs_expr = parser_parse_lhs(self, &assignment_or_rhs); - if(!assignment_or_rhs) { + lhs_expr = parser_parse_declaration_lhs(self); + if(lhs_expr) { + bool match; throw_if_error(ast_create(self->allocator, lhs_expr, AST_LHS, &result)); - return result; - } - - if(!lhs_expr) + if(lhs_expr->is_extern) { + throw_if_error(tokenizer_accept(&self->tokenizer, TOK_SEMICOLON)); + return result; + } else { + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_SEMICOLON, &match)); + if(match) + return result; + } + + throw_if_error(tokenizer_accept(&self->tokenizer, TOK_EQUALS)); + + FunctionDecl *func_decl; + StructDecl *struct_decl; + Import *import; + + func_decl = parser_parse_closure(self); + if(func_decl) { + throw_if_error(ast_create(self->allocator, func_decl, AST_FUNCTION_DECL, &lhs_expr->rhs_expr)); + return result; + } + + struct_decl = parser_parse_struct_decl(self); + if(struct_decl) { + throw_if_error(ast_create(self->allocator, struct_decl, AST_STRUCT_DECL, &lhs_expr->rhs_expr)); + return result; + } + + import = parser_parse_import(self); + if(import) { + parser_queue_file(self, import->path, &import->file_scope); + throw_if_error(ast_create(self->allocator, import, AST_IMPORT, &lhs_expr->rhs_expr)); + parser_parse_body_semicolon(self, lhs_expr->rhs_expr); + return result; + } + } else { self->error_context = ERROR_CONTEXT_NO_LHS; + result = parser_parse_conditional(self); + if(result) + return result; + } - rhs_expr = parser_parse_rhs_start(self, !lhs_expr); + self->error_context = ERROR_CONTEXT_RHS_STANDALONE; + rhs_expr = parser_parse_rhs(self); + self->error_context = ERROR_CONTEXT_NONE; if(lhs_expr) { lhs_expr->rhs_expr = rhs_expr; - throw_if_error(ast_create(self->allocator, lhs_expr, AST_LHS, &result)); } else { - result = rhs_expr; + bool match; + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_EQUALS, &match)); + if(match) { + AssignmentExpr *assign_expr; + throw_if_error(scoped_allocator_alloc(self->allocator, sizeof(AssignmentExpr), (void**)&assign_expr)); + throw_if_error(ast_create(self->allocator, assign_expr, AST_ASSIGN, &result)); + assign_expr->lhs_expr = rhs_expr; + + self->error_context = ERROR_CONTEXT_RHS_STANDALONE; + assign_expr->rhs_expr = parser_parse_rhs(self); + self->error_context = ERROR_CONTEXT_NONE; + } else { + result = rhs_expr; + } } parser_parse_body_semicolon(self, rhs_expr); diff --git a/src/ssa/ssa.c b/src/ssa/ssa.c index c4ed0d3..91ba185 100644 --- a/src/ssa/ssa.c +++ b/src/ssa/ssa.c @@ -4,6 +4,7 @@ #include "../../include/std/hash.h" #include "../../include/std/thread.h" #include "../../include/ast.h" +#include "../../include/compiler.h" #include #define throw(result) do { longjmp(context->env, (result)); } while(0) @@ -59,7 +60,7 @@ int ssa_init(Ssa *self, ScopedAllocator *allocator) { return 0; } -int ssa_get_unique_reg(Ssa *self, SsaRegister *result) { +static CHECK_RESULT int ssa_get_unique_reg(Ssa *self, SsaRegister *result) { /* Overflow */ if(self->reg_counter + 1 < self->reg_counter) return -1; @@ -144,12 +145,13 @@ static CHECK_RESULT int ssa_add_ins_form1(Ssa *self, SsaInstruction ins_type, Ss } static const char* binop_type_to_string(SsaInstruction binop_type) { - assert(binop_type >= SSA_ADD && binop_type <= SSA_DIV); + assert(binop_type >= SSA_ADD && binop_type <= SSA_EQUALS); switch(binop_type) { case SSA_ADD: return "+"; case SSA_SUB: return "-"; case SSA_MUL: return "*"; case SSA_DIV: return "/"; + case SSA_EQUALS: return "=="; default: return ""; } } @@ -173,31 +175,31 @@ static CHECK_RESULT int ssa_add_ins_form2(Ssa *self, SsaInstruction ins_type, Ss return 0; } -int ssa_ins_assign_inter(Ssa *self, SsaRegister dest, SsaNumber number) { +static CHECK_RESULT int ssa_ins_assign_inter(Ssa *self, SsaRegister dest, SsaNumber number) { SsaIntermediateIndex index; return_if_error(ssa_try_add_intermediate(self, number, &index)); amal_log_debug("r%u = i%u", dest, index); return ssa_add_ins_form1(self, SSA_ASSIGN_INTER, dest, index); } -int ssa_ins_assign_string(Ssa *self, SsaRegister dest, BufferView str) { +static CHECK_RESULT int ssa_ins_assign_string(Ssa *self, SsaRegister dest, BufferView str) { SsaStringIndex index; return_if_error(ssa_try_add_string(self, str, &index)); amal_log_debug("r%u = s%u", dest, index); return ssa_add_ins_form1(self, SSA_ASSIGN_STRING, dest, index); } -int ssa_ins_assign_reg(Ssa *self, SsaRegister dest, SsaRegister src) { +static CHECK_RESULT int ssa_ins_assign_reg(Ssa *self, SsaRegister dest, SsaRegister src) { amal_log_debug("r%u = r%u", dest, src); return ssa_add_ins_form1(self, SSA_ASSIGN_REG, dest, src); } -int ssa_ins_binop(Ssa *self, SsaInstruction binop_type, SsaRegister lhs, SsaRegister rhs, SsaRegister *result) { - assert(binop_type >= SSA_ADD && binop_type <= SSA_DIV); +static CHECK_RESULT int ssa_ins_binop(Ssa *self, SsaInstruction binop_type, SsaRegister lhs, SsaRegister rhs, SsaRegister *result) { + assert(binop_type >= SSA_ADD && binop_type <= SSA_EQUALS); return ssa_add_ins_form2(self, binop_type, lhs, rhs, result); } -int ssa_ins_func_start(Ssa *self, u8 num_args, SsaFuncIndex *result) { +static CHECK_RESULT int ssa_ins_func_start(Ssa *self, u8 num_args, SsaFuncIndex *result) { usize index; index = self->instructions.size; @@ -214,14 +216,14 @@ int ssa_ins_func_start(Ssa *self, u8 num_args, SsaFuncIndex *result) { return 0; } -int ssa_ins_func_end(Ssa *self) { +static CHECK_RESULT int ssa_ins_func_end(Ssa *self) { u8 ins; ins = SSA_FUNC_END; amal_log_debug("FUNC_END"); return buffer_append(&self->instructions, &ins, 1); } -int ssa_ins_push(Ssa *self, SsaRegister reg) { +static CHECK_RESULT int ssa_ins_push(Ssa *self, SsaRegister reg) { usize index; index = self->instructions.size; @@ -232,7 +234,7 @@ int ssa_ins_push(Ssa *self, SsaRegister reg) { return 0; } -int ssa_ins_call(Ssa *self, FunctionDecl *func_decl, SsaRegister *result) { +static CHECK_RESULT int ssa_ins_call(Ssa *self, FunctionDecl *func_decl, SsaRegister *result) { usize index; index = self->instructions.size; @@ -249,31 +251,88 @@ int ssa_ins_call(Ssa *self, FunctionDecl *func_decl, SsaRegister *result) { return 0; } +static CHECK_RESULT int ssa_ins_jumpzero(Ssa *self, SsaRegister condition_reg, JumpOffset jump_offset) { + usize index; + index = self->instructions.size; + + return_if_error(buffer_append_empty(&self->instructions, sizeof(u8) + sizeof(SsaRegister) + sizeof(JumpOffset))); + self->instructions.data[index + 0] = SSA_JUMP_ZERO; + am_memcpy(self->instructions.data + index + 1, &condition_reg, sizeof(SsaRegister)); + am_memcpy(self->instructions.data + index + 1 + sizeof(SsaRegister), &jump_offset, sizeof(JumpOffset)); + if(jump_offset == 0) + amal_log_debug("JUMP_ZERO r%u, DUMMY", condition_reg); + else + amal_log_debug("JUMP_ZERO r%u, %d", condition_reg, jump_offset); + return 0; +} + +static CHECK_RESULT int ssa_ins_jump(Ssa *self, JumpOffset jump_offset) { + usize index; + index = self->instructions.size; + + return_if_error(buffer_append_empty(&self->instructions, sizeof(u8) + sizeof(JumpOffset))); + self->instructions.data[index + 0] = SSA_JUMP; + am_memcpy(self->instructions.data + index + 1, &jump_offset, sizeof(JumpOffset)); + amal_log_debug("JUMP %d", jump_offset); + return 0; +} + +static usize ssa_ins_get_index(Ssa *self) { + return self->instructions.size; +} + +/* Set target of jump instruction to current location */ +static CHECK_RESULT int ssa_ins_jump_set_target(Ssa *self, usize jump_ins_index) { + switch(self->instructions.data[jump_ins_index]) { + case SSA_JUMP_ZERO: { + isize jump_offset = (isize)ssa_ins_get_index(self) - (isize)jump_ins_index; + /* TODO: Should something be done about this? */ + if(jump_offset < -0x7FFF || jump_offset > 0x7FFF) { + amal_log_error("Unexpected error. Jump offset has to be less than +-32767, was %d", jump_offset); + return 1; + } + am_memcpy(self->instructions.data + jump_ins_index + 1 + sizeof(SsaRegister), &jump_offset, sizeof(JumpOffset)); + break; + } + default: + assert(bool_false && "Unexpected error... jump_ins_index doesn't point to a valid index to a jump instruction"); + break; + } + return 0; +} + static CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context); static CHECK_RESULT SsaRegister number_generate_ssa(Number *self, SsaCompilerContext *context) { SsaRegister reg; SsaNumber number; - if(self->is_integer) { + if(self->is_integer) number = create_ssa_integer(self->value.integer); - throw_if_error(ssa_get_unique_reg(context->ssa, ®)); - throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); - } else { + else number = create_ssa_float(self->value.floating); - throw_if_error(ssa_get_unique_reg(context->ssa, ®)); - throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); - } + throw_if_error(ssa_get_unique_reg(context->ssa, ®)); + throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); return reg; } +static CHECK_RESULT SsaRegister lhsexpr_extern_generate_ssa(LhsExpr *self, SsaCompilerContext *context) { + /* TODO: SsaRegister should be extended to include static and extern data */ + (void)self; + (void)context; + amal_log_error("TODO: Implement lhsexpr_extern_generate_ssa"); + return 0; +} + static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(Ast *self, SsaCompilerContext *context) { - /* TODO: Implement */ LhsExpr *lhs_expr; SsaRegister reg; assert(self->type == AST_LHS); lhs_expr = self->value.lhs_expr; + if(lhs_expr->is_extern) + return lhsexpr_extern_generate_ssa(lhs_expr, context); + if(lhs_expr->rhs_expr) { SsaRegister rhs_reg; rhs_reg = ast_generate_ssa(lhs_expr->rhs_expr, context); @@ -283,8 +342,10 @@ static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(Ast *self, SsaCompilerConte Import expression also has no meaning in SSA until it's used. TODO: Shouldn't lhsexpr that have struct/function declaration as rhs be different ast expression types? */ - if(self->resolve_data.type == lhs_expr || lhs_expr->rhs_expr->type == AST_IMPORT) + if(self->resolve_data.type == lhs_expr || lhs_expr->rhs_expr->type == AST_IMPORT) { + /*assert(bool_false);*/ return 0; + } throw_if_error(ssa_get_unique_reg(context->ssa, ®)); if(reg == rhs_reg) { amal_log_error("rhs_expr is same as reg.. rhs type: %d", lhs_expr->rhs_expr->type); @@ -292,12 +353,33 @@ static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(Ast *self, SsaCompilerConte assert(reg != rhs_reg); throw_if_error(ssa_ins_assign_reg(context->ssa, reg, rhs_reg)); } else { - /* TODO: assign default value to reg depending on LhsExpr type */ - reg = 0; + /* TODO: Do not assign if we dont want default value */ + SsaNumber number; + if(self->resolve_data.type == context->compiler->default_types.i64) + number = create_ssa_integer(0); + else if(self->resolve_data.type == context->compiler->default_types.f64) + number = create_ssa_float(0.0); + else + assert(bool_false && "TODO: assign default value to reg depending on LhsExpr type"); + throw_if_error(ssa_get_unique_reg(context->ssa, ®)); + throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); } return reg; } +static CHECK_RESULT SsaRegister assignmentexpr_generate_ssa(Ast *ast, SsaCompilerContext *context) { + AssignmentExpr *self; + SsaRegister lhs_reg, rhs_reg; + + assert(ast->type == AST_ASSIGN); + self = ast->value.assign_expr; + + lhs_reg = ast_generate_ssa(self->lhs_expr, context); + rhs_reg = ast_generate_ssa(self->rhs_expr, context); + throw_if_error(ssa_ins_assign_reg(context->ssa, lhs_reg, rhs_reg)); + return lhs_reg; +} + /* TODO: Each function declaration should be in separate SSA instances so ast can be converted into ssa in any order. @@ -314,6 +396,7 @@ static CHECK_RESULT SsaRegister funcdecl_generate_ssa(FunctionDecl *self, SsaCom throw_if_error(ssa_ins_func_end(context->ssa)); context->ssa->reg_counter = prev_reg_counter; + /*assert(bool_false);*/ return 0; } @@ -348,12 +431,14 @@ static CHECK_RESULT SsaRegister funccall_generate_ssa(Ast *self, SsaCompilerCont static CHECK_RESULT SsaRegister structdecl_generate_ssa(StructDecl *self, SsaCompilerContext *context) { /* TODO: Implement */ + /*assert(bool_false);*/ scope_generate_ssa(&self->body, context); return 0; } static CHECK_RESULT SsaRegister structfield_generate_ssa(StructField *self, SsaCompilerContext *context) { /* TODO: Implement */ + /*assert(bool_false);*/ (void)self; (void)context; return 0; @@ -367,10 +452,10 @@ static CHECK_RESULT SsaRegister string_generate_ssa(String *self, SsaCompilerCon } static CHECK_RESULT SsaRegister variable_generate_ssa(Variable *self, SsaCompilerContext *context) { - /* TODO: Implement, and with cross field references */ - (void)self; - (void)context; - return 0; + /* TODO: If resolved_var refers to a variable in another file, use a cross file reference that requires no locking (not yet implemented) */ + /* This is not thread-safe:*/ + assert(self->resolved_var); + return ast_generate_ssa(self->resolved_var, context); } static SsaInstruction binop_type_to_ssa_type(BinopType binop_type) { @@ -386,6 +471,8 @@ static SsaInstruction binop_type_to_ssa_type(BinopType binop_type) { case BINOP_DOT: assert(bool_false && "Binop dot not valid for arithmetic operation and requires special functionality"); return 0; + case BINOP_EQUALS: + return SSA_EQUALS; } return 0; } @@ -405,7 +492,57 @@ static CHECK_RESULT SsaRegister binop_generate_ssa(Binop *self, SsaCompilerConte return reg; } -CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context) { +static void else_if_statement_generate_ssa(ElseIfStatement *else_if_stmt, SsaCompilerContext *context) { + usize jump_ins_index; + if(else_if_stmt->condition) { + SsaRegister condition_reg; + condition_reg = ast_generate_ssa(else_if_stmt->condition, context); + jump_ins_index = ssa_ins_get_index(context->ssa); + throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); + } + scope_generate_ssa(&else_if_stmt->body, context); + if(else_if_stmt->condition) + throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_ins_index)); + if(else_if_stmt->next_else_if_stmt) + else_if_statement_generate_ssa(else_if_stmt->next_else_if_stmt, context); +} + +static void if_statement_generate_ssa(IfStatement *if_stmt, SsaCompilerContext *context) { + SsaRegister condition_reg; + usize jump_ins_index; + + condition_reg = ast_generate_ssa(if_stmt->condition, context); + jump_ins_index = ssa_ins_get_index(context->ssa); + throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); + scope_generate_ssa(&if_stmt->body, context); + throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_ins_index)); + if(if_stmt->else_if_stmt) + else_if_statement_generate_ssa(if_stmt->else_if_stmt, context); +} + +static void while_statement_generate_ssa(WhileStatement *while_stmt, SsaCompilerContext *context) { + SsaRegister condition_reg; + usize jump_back_ins_index; + usize jump_condition_ins_index; + isize jump_offset; + + jump_back_ins_index = ssa_ins_get_index(context->ssa); + condition_reg = ast_generate_ssa(while_stmt->condition, context); + jump_condition_ins_index = ssa_ins_get_index(context->ssa); + throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); + scope_generate_ssa(&while_stmt->body, context); + /* Jump back and check condition again before running the content of the loop again */ + jump_offset = (isize)jump_back_ins_index - (isize)ssa_ins_get_index(context->ssa); + /* TODO: Should something be done about this? */ + if(jump_offset < -0x7FFF || jump_offset > 0x7FFF) { + amal_log_error("Unexpected error. Jump offset has to be less than +-32767, was %d", jump_offset); + throw(1); + } + throw_if_error(ssa_ins_jump(context->ssa, (JumpOffset)jump_offset)); + throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_condition_ins_index)); +} + +static CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context) { assert(self); #ifdef DEBUG if(self->resolve_data.status != AST_RESOLVED && self->resolve_data.status != AST_SSA_RESOLVED) { @@ -420,48 +557,48 @@ CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context switch(self->type) { case AST_NUMBER: self->ssa_reg = number_generate_ssa(self->value.number, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_FUNCTION_DECL: self->ssa_reg = funcdecl_generate_ssa(self->value.func_decl, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_FUNCTION_CALL: self->ssa_reg = funccall_generate_ssa(self, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_STRUCT_DECL: self->ssa_reg = structdecl_generate_ssa(self->value.struct_decl, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_STRUCT_FIELD: self->ssa_reg = structfield_generate_ssa(self->value.struct_field, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_LHS: self->ssa_reg = lhsexpr_generate_ssa(self, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; + case AST_ASSIGN: + self->ssa_reg = assignmentexpr_generate_ssa(self, context); + break; case AST_IMPORT: /* TODO: Implement cross file references */ self->ssa_reg = 0; - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_STRING: self->ssa_reg = string_generate_ssa(self->value.string, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_VARIABLE: self->ssa_reg = variable_generate_ssa(self->value.variable, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; case AST_BINOP: self->ssa_reg = binop_generate_ssa(self->value.binop, context); - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + break; + case AST_IF_STATEMENT: + if_statement_generate_ssa(self->value.if_stmt, context); + break; + case AST_WHILE_STATEMENT: + while_statement_generate_ssa(self->value.while_stmt, context); + break; } - return 0; + self->resolve_data.status = AST_SSA_RESOLVED; + return self->ssa_reg; } void scope_generate_ssa(Scope *self, SsaCompilerContext *context) { diff --git a/src/tokenizer.c b/src/tokenizer.c index 7f6d08e..03a72a1 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -170,6 +170,9 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { if(am_memeql(self->value.identifier.data, "fn", 2)) { *token = TOK_FN; return TOKENIZER_OK; + } else if(am_memeql(self->value.identifier.data, "if", 2)) { + *token = TOK_IF; + return TOKENIZER_OK; } break; } @@ -183,10 +186,20 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { } break; } + case 4: { + if(am_memeql(self->value.identifier.data, "else", 4)) { + *token = TOK_ELSE; + return TOKENIZER_OK; + } + break; + } case 5: { if(am_memeql(self->value.identifier.data, "const", 5)) { *token = TOK_CONST; return TOKENIZER_OK; + } else if(am_memeql(self->value.identifier.data, "while", 5)) { + *token = TOK_WHILE; + return TOKENIZER_OK; } break; } @@ -194,6 +207,9 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { if(am_memeql(self->value.identifier.data, "struct", 6)) { *token = TOK_STRUCT; return TOKENIZER_OK; + } else if(am_memeql(self->value.identifier.data, "extern", 6)) { + *token = TOK_EXTERN; + return TOKENIZER_OK; } break; } @@ -288,7 +304,12 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { SET_BINOP(BINOP_DIV); } else if(c == '=') { ++self->index; - *token = TOK_EQUALS; + if(self->index < (int)self->code.size && tokenizer_get_char(self) == '=') { + ++self->index; + SET_BINOP(BINOP_EQUALS); + } else { + *token = TOK_EQUALS; + } } else if(c == '(') { ++self->index; *token = TOK_OPEN_PAREN; @@ -450,6 +471,18 @@ static BufferView tokenizer_expected_token_as_string(Token token) { case TOK_PUB: str = "pub"; break; + case TOK_IF: + str = "if"; + break; + case TOK_ELSE: + str = "else"; + break; + case TOK_WHILE: + str = "while"; + break; + case TOK_EXTERN: + str = "extern"; + break; } return create_buffer_view(str, strlen(str)); } @@ -588,18 +621,15 @@ int tokenizer_get_end_of_multiline_comment(Tokenizer *self, int index) { int comment_count; comment_count = 1; + ++index; while(index < (int)self->code.size) { c = self->code.data[index]; - if(c == '*') { - if(index - 1 >= 0 && self->code.data[index - 1] == '/') { - ++comment_count; - } - } else if(c == '/') { - if(index - 1 >= 0 && self->code.data[index - 1] == '*') { - --comment_count; - if(comment_count == 0) - return index + 1; - } + if(c == '*' && self->code.data[index - 1] == '/') { + ++comment_count; + } else if(c == '/' && self->code.data[index - 1] == '*') { + --comment_count; + if(comment_count == 0) + return index + 1; } ++index; } @@ -625,7 +655,61 @@ static int max(int a, int b) { return a > b ? a : b; } +void tokenizer_print_error_args(Tokenizer *self, int index, const char *fmt, va_list args) { + int line; + int line_start; + int line_end; + /*int code_start;*/ + int prev_column; + int i; + + line = tokenizer_get_line_by_index(self, index); + line_start = tokenizer_get_start_of_line_from_index(self, index); + line_end = tokenizer_get_end_of_line_from_index(self, index); + /*code_start = find_non_whitespace(&self->code.data[line_start], line_end - line_start); + if(code_start != -1) + line_start += code_start;*/ + prev_column = index - line_start; + + if(self->compiler_options->error_callback) { + char buffer[2048]; + int bytes_copied; + + bytes_copied = 0; + bytes_copied += max(0, snprintf(buffer + bytes_copied, sizeof(buffer) - bytes_copied, "%.*s:%d:%d: error: ", (int)self->code_name.size, self->code_name.data, line, 1 + prev_column)); + + if(sizeof(buffer) - bytes_copied > 0) { + bytes_copied += max(0, vsnprintf(buffer + bytes_copied, sizeof(buffer) - bytes_copied, fmt, args)); + } + + if(sizeof(buffer) - bytes_copied > 0) + bytes_copied += max(0, snprintf(buffer + bytes_copied, sizeof(buffer) - bytes_copied, "\n%.*s\n", line_end - line_start, self->code.data + line_start)); + + if(sizeof(buffer) - bytes_copied > 0) { + for(i = 0; i < prev_column; ++i) + bytes_copied += max(0, snprintf(buffer + bytes_copied, sizeof(buffer) - bytes_copied, " ")); + } + + if(sizeof(buffer) - bytes_copied > 0) + bytes_copied += max(0, snprintf(buffer + bytes_copied, sizeof(buffer) - bytes_copied, "^\n")); + + self->compiler_options->error_callback(buffer, bytes_copied, self->compiler_options->error_callback_userdata); + } else { + amal_mutex *mutex; + mutex = amal_log_get_mutex(); + ignore_result_int(amal_mutex_lock(mutex, "tokenizer_print_error")); + fprintf(stderr, "\x1b[1;37m%.*s:%d:%d:\x1b[0m \x1b[1;31merror:\x1b[0m ", (int)self->code_name.size, self->code_name.data, line, 1 + prev_column); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n%.*s\n", line_end - line_start, self->code.data + line_start); + for(i = 0; i < prev_column; ++i) + fprintf(stderr, " "); + fprintf(stderr, "\x1b[1;32m^\x1b[0m\n"); + ignore_result_int(amal_mutex_unlock(mutex)); + } +} + void tokenizer_print_error(Tokenizer *self, int index, const char *fmt, ...) { +#if 0 va_list args; int line; int line_start; @@ -681,6 +765,11 @@ void tokenizer_print_error(Tokenizer *self, int index, const char *fmt, ...) { va_end(args); ignore_result_int(amal_mutex_unlock(mutex)); } +#endif + va_list args; + va_start(args, fmt); + tokenizer_print_error_args(self, index, fmt, args); + va_end(args); } void tokenizer_print_error_object(Tokenizer *self, TokenizerError *error) { -- cgit v1.2.3