diff options
-rw-r--r-- | include/compiler.h | 12 | ||||
-rw-r--r-- | include/compiler_options.h | 11 | ||||
-rw-r--r-- | include/parser.h | 1 | ||||
-rw-r--r-- | include/std/buffer.h | 2 | ||||
-rw-r--r-- | include/tokenizer.h | 4 | ||||
-rw-r--r-- | src/compiler.c | 94 | ||||
-rw-r--r-- | src/parser.c | 5 | ||||
-rw-r--r-- | src/std/buffer.c | 4 | ||||
-rw-r--r-- | src/tokenizer.c | 81 | ||||
-rw-r--r-- | tests/errors/closure_no_lhs.amal | 1 | ||||
-rw-r--r-- | tests/errors/duplicate_declaration.amal | 2 | ||||
-rw-r--r-- | tests/errors/pub_in_closure.amal | 3 | ||||
-rw-r--r-- | tests/main.amal | 3 | ||||
-rw-r--r-- | tests/main.c | 127 |
14 files changed, 297 insertions, 53 deletions
diff --git a/include/compiler.h b/include/compiler.h index b80d7c1..3a31ad9 100644 --- a/include/compiler.h +++ b/include/compiler.h @@ -6,7 +6,7 @@ #include "std/buffer_view.h" #include "std/scoped_allocator.h" #include "std/thread.h" -#include "defs.h" +#include "compiler_options.h" #include "ast.h" #define AMAL_COMPILER_OK 0 @@ -31,6 +31,7 @@ typedef struct { struct amal_compiler { amal_default_types default_types; + amal_compiler_options options; ScopedAllocator allocator; Scope root_scope; Buffer/*<Parser*>*/ parsers; @@ -39,17 +40,22 @@ struct amal_compiler { ParserThreadData *threads; int usable_thread_count; bool started; + bool used; amal_mutex mutex; int generic_work_object_index; }; -CHECK_RESULT int amal_compiler_init(amal_compiler *self); +void amal_compiler_options_init(amal_compiler_options *self); + +/* If @options is NULL, then default values are used */ +CHECK_RESULT int amal_compiler_init(amal_compiler *self, const amal_compiler_options *options); CHECK_RESULT int amal_compiler_deinit(amal_compiler *self); /* This function creates a copy of @filepath in the same thread it's called from so it doesn't have to survive longer than this function call. */ -CHECK_RESULT int amal_compiler_load_file(amal_compiler *self, const char *filepath, FileScopeReference **file_scope); +CHECK_RESULT int amal_compiler_load_file(amal_compiler *self, const char *filepath); +CHECK_RESULT int amal_compiler_internal_load_file(amal_compiler *self, const char *filepath, FileScopeReference **file_scope); /* TODO: amal_compiler_unload_file */ #endif diff --git a/include/compiler_options.h b/include/compiler_options.h new file mode 100644 index 0000000..1854372 --- /dev/null +++ b/include/compiler_options.h @@ -0,0 +1,11 @@ +#ifndef AMALGAM_COMPILER_OPTIONS_H +#define AMALGAM_COMPILER_OPTIONS_H + +typedef void(*amal_compiler_error_callback)(const char *err_msg, int err_msg_len, void *userdata); + +typedef struct { + amal_compiler_error_callback error_callback; + void *error_callback_userdata; +} amal_compiler_options; + +#endif diff --git a/include/parser.h b/include/parser.h index 0261800..8a339ff 100644 --- a/include/parser.h +++ b/include/parser.h @@ -41,6 +41,7 @@ struct Parser { bool has_func_parent; ScopedAllocator *allocator; /* borrowed. Copied from @compiler for faster access to allocator */ amal_compiler *compiler; + SsaCompilerContext *ssa_context; bool started; TokenizerError error; ErrorContext error_context; diff --git a/include/std/buffer.h b/include/std/buffer.h index a1bfb01..723ef6c 100644 --- a/include/std/buffer.h +++ b/include/std/buffer.h @@ -20,6 +20,8 @@ CHECK_RESULT int buffer_init(Buffer *self, struct ScopedAllocator *allocator); CHECK_RESULT int buffer_append(Buffer *self, const void *data, usize size); void* buffer_get(Buffer *self, usize index, usize type_size); CHECK_RESULT int buffer_pop(Buffer *self, void *data, usize size); +/* Set buffer size to 0, doesn't reallocate */ +void buffer_clear(Buffer *self); void* buffer_start(Buffer *self); void* buffer_end(Buffer *self); usize __buffer_get_size(Buffer *self, usize type_size); diff --git a/include/tokenizer.h b/include/tokenizer.h index 3944ed1..d10b789 100644 --- a/include/tokenizer.h +++ b/include/tokenizer.h @@ -5,6 +5,7 @@ #include "std/misc.h" #include "std/defs.h" #include "binop_type.h" +#include "compiler_options.h" #define TOKENIZER_OK 0 /* General error */ @@ -55,6 +56,7 @@ typedef struct { } value; bool number_is_integer; ScopedAllocator *allocator; /* borrowed */ + const amal_compiler_options *compiler_options; /* borrowed */ } Tokenizer; typedef struct { @@ -62,7 +64,7 @@ typedef struct { char* str; } TokenizerError; -CHECK_RESULT int tokenizer_init(Tokenizer *self, ScopedAllocator *allocator, BufferView code, BufferView code_name); +CHECK_RESULT int tokenizer_init(Tokenizer *self, ScopedAllocator *allocator, BufferView code, BufferView code_name, const amal_compiler_options *compiler_options); CHECK_RESULT int tokenizer_accept(Tokenizer *self, Token expected_token); /* @result is set to 0 if the next token is equal to @expected_token, diff --git a/src/compiler.c b/src/compiler.c index c9ae09a..d10d9fb 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -11,8 +11,6 @@ #include <limits.h> #include <assert.h> -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - static CHECK_RESULT int get_thread_count_env_var(int *thread_count) { char *threads; threads = getenv("THREADS"); @@ -68,7 +66,12 @@ static CHECK_RESULT int init_default_types(amal_compiler *compiler) { return 0; } -int amal_compiler_init(amal_compiler *self) { +void amal_compiler_options_init(amal_compiler_options *self) { + self->error_callback = NULL; + self->error_callback_userdata = NULL; +} + +int amal_compiler_init(amal_compiler *self, const amal_compiler_options *options) { int i; int result; @@ -87,7 +90,12 @@ int amal_compiler_init(amal_compiler *self) { am_memset(&self->allocator, 0, sizeof(self->allocator)); am_memset(&self->root_scope, 0, sizeof(self->root_scope)); + if(options) + am_memcpy(&self->options, options, sizeof(self->options)); + else + am_memset(&self->options, 0, sizeof(self->options)); self->started = bool_false; + self->used = bool_false; self->generic_work_object_index = 0; amal_mutex_init(&self->mutex); @@ -129,7 +137,8 @@ int amal_compiler_deinit(amal_compiler *self) { typedef enum { THREAD_WORK_PARSE, THREAD_WORK_RESOLVE_AST, - THREAD_WORK_GENERATE_SSA + THREAD_WORK_GENERATE_SSA, + THREAD_WORK_GENERATE_BYTECODE } ThreadWorkType; typedef struct { @@ -153,7 +162,7 @@ typedef struct { ThreadWorkType type; } ThreadWorkData; -static CHECK_RESULT int amal_compiler_load_in_this_thread(amal_compiler *self, FileScopeReference *file_scope, ScopedAllocator *allocator) { +static CHECK_RESULT int amal_compiler_load_in_this_thread(amal_compiler *compiler, FileScopeReference *file_scope, ScopedAllocator *allocator) { Parser *parser; int result; BufferView filepath; @@ -162,16 +171,16 @@ static CHECK_RESULT int amal_compiler_load_in_this_thread(amal_compiler *self, F filepath = create_buffer_view(file_scope->canonical_path.data, file_scope->canonical_path.size); amal_log_info("Started parsing %.*s", filepath.size, filepath.data); return_if_error(scoped_allocator_alloc(allocator, sizeof(Parser), (void**)&parser)); - return_if_error(parser_init(parser, self, allocator)); + return_if_error(parser_init(parser, compiler, allocator)); file_scope->parser = parser; return_if_error(parser_parse_file(parser, filepath)); - cleanup_if_error(amal_mutex_lock(&self->mutex, "amal_compiler_load_in_this_thread")); - cleanup_if_error(buffer_append(&self->parsers, &parser, sizeof(parser))); + cleanup_if_error(amal_mutex_lock(&compiler->mutex, "amal_compiler_load_in_this_thread")); + cleanup_if_error(buffer_append(&compiler->parsers, &parser, sizeof(parser))); amal_log_info("Finished parsing %.*s", filepath.size, filepath.data); result = AMAL_COMPILER_OK; cleanup: - amal_mutex_tryunlock(&self->mutex); + amal_mutex_tryunlock(&compiler->mutex); return result; } @@ -200,6 +209,14 @@ static void* thread_callback_parse_file(void *userdata) { result = NULL; cleanup: + /* + To stop all other parsers from working cleanly, we simply clear the file queue, + and the other threads will stop when they are done with the file they are currently parsing. + */ + if(result != NULL) { + ignore_result_int(amal_mutex_lock(&compiler_parser_userdata.compiler->mutex, "thread_callback_parse_file")); + buffer_clear(&compiler_parser_userdata.compiler->queued_files); + } compiler_parser_userdata.parser_thread_data->status = PARSER_THREAD_STATUS_IDLE; amal_mutex_tryunlock(&compiler_parser_userdata.compiler->mutex); return result; @@ -219,17 +236,30 @@ static CHECK_RESULT int thread_resolve_ast(amal_compiler *compiler, Parser *pars } static CHECK_RESULT int thread_generate_ssa(Parser *parser) { - SsaCompilerContext compiler_context; + SsaCompilerContext *compiler_context; int result; - result = setjmp(compiler_context.env); - if(result == 0) { - return_if_error(ssa_init(&compiler_context.ssa, parser->allocator)); - amal_log_debug("Generating SSA for file: %.*s", parser->tokenizer.code_name.size, parser->tokenizer.code_name.data); - scope_generate_ssa(&parser->struct_decl.body, &compiler_context); - } + + return_if_error(scoped_allocator_alloc(parser->allocator, sizeof(SsaCompilerContext), (void**)&compiler_context)); + return_if_error(ssa_init(&compiler_context->ssa, parser->allocator)); + /* TODO: Maybe instead of creating a ssa context for every parser (every file), create one for every thread */ + parser->ssa_context = compiler_context; + amal_log_debug("Generating SSA for file: %.*s", parser->tokenizer.code_name.size, parser->tokenizer.code_name.data); + result = setjmp(compiler_context->env); + + if(result == 0) + scope_generate_ssa(&parser->struct_decl.body, compiler_context); + return result; } +static CHECK_RESULT int thread_generate_bytecode(amal_compiler *compiler, Parser *parser) { + /* TODO: Implement */ + (void)compiler; + (void)parser; + /*assert(bool_false);*/ + return 0; +} + /* TODO: Handle errors (stop work in all other threads and report errors/warnings) */ static void* thread_callback_generic(void *userdata) { CompilerGenericThreadUserData compiler_userdata; @@ -254,6 +284,9 @@ static void* thread_callback_generic(void *userdata) { case THREAD_WORK_GENERATE_SSA: cleanup_if_error(thread_generate_ssa(parser)); break; + case THREAD_WORK_GENERATE_BYTECODE: + cleanup_if_error(thread_generate_bytecode(compiler_userdata.compiler, parser)); + break; } cleanup_if_error(amal_mutex_lock(&compiler_userdata.compiler->mutex, "thread_callback_generic")); if(compiler_userdata.compiler->generic_work_object_index + 1 >= (int)buffer_get_size(&compiler_userdata.compiler->parsers, Parser*)) @@ -265,6 +298,14 @@ static void* thread_callback_generic(void *userdata) { result = NULL; cleanup: + /* + To stop all other worker threads cleanly, we simply say we are done with all work in the queue, + and the other threads will stop when they are done with the work they are currently working on. + */ + if(result != NULL) { + cleanup_if_error(amal_mutex_lock(&compiler_userdata.compiler->mutex, "thread_callback_generic")); + compiler_userdata.compiler->generic_work_object_index = (int)buffer_get_size(&compiler_userdata.compiler->parsers, Parser*); + } compiler_userdata.parser_thread_data->status = PARSER_THREAD_STATUS_IDLE; amal_mutex_tryunlock(&compiler_userdata.compiler->mutex); return result; @@ -297,7 +338,8 @@ static CHECK_RESULT int amal_compiler_select_thread_for_work(amal_compiler *self break; } case THREAD_WORK_RESOLVE_AST: - case THREAD_WORK_GENERATE_SSA: { + case THREAD_WORK_GENERATE_SSA: + case THREAD_WORK_GENERATE_BYTECODE: { CompilerGenericThreadUserData *userdata; cleanup_if_error(am_malloc(sizeof(CompilerGenericThreadUserData), (void**)&userdata)); thread_user_data = userdata; @@ -432,11 +474,15 @@ static CHECK_RESULT int try_create_file_scope(amal_compiler *compiler, const cha return ret; } -/* -amal_compiler_load_file is called by the user for the first file to compile -but also called by the parser when it sees @import -*/ -int amal_compiler_load_file(amal_compiler *self, const char *filepath, FileScopeReference **file_scope) { +int amal_compiler_load_file(amal_compiler *self, const char *filepath) { + FileScopeReference *file_scope; + if(self->used) + return AMAL_COMPILER_ERR; + self->used = bool_true; + return amal_compiler_internal_load_file(self, filepath, &file_scope); +} + +int amal_compiler_internal_load_file(amal_compiler *self, const char *filepath, FileScopeReference **file_scope) { int result; BufferView filepath_view; ParserThreadData *parser_thread_data; @@ -478,6 +524,10 @@ int amal_compiler_load_file(amal_compiler *self, const char *filepath, FileScope assert(amal_compiler_check_all_threads_done(self)); amal_log_info("Finished generating SSA"); + return_if_error(amal_compiler_dispatch_generic(self, THREAD_WORK_GENERATE_BYTECODE)); + assert(amal_compiler_check_all_threads_done(self)); + amal_log_info("Finished generating bytecode"); + return AMAL_COMPILER_OK; } diff --git a/src/parser.c b/src/parser.c index bac7b3a..9aa8924 100644 --- a/src/parser.c +++ b/src/parser.c @@ -54,6 +54,7 @@ int parser_thread_data_join(ParserThreadData *self, void **result) { int parser_init(Parser *self, amal_compiler *compiler, ScopedAllocator *allocator) { self->allocator = allocator; self->compiler = compiler; + self->ssa_context = NULL; self->started = bool_false; self->error.index = 0; self->error.str = NULL; @@ -571,7 +572,7 @@ ROOT = BODY_LOOP int parser_parse_buffer(Parser *self, BufferView code_buffer, BufferView buffer_name) { int result; self->file_decl.var_name = buffer_name; - throw_if_error(tokenizer_init(&self->tokenizer, self->allocator, code_buffer, buffer_name)); + throw_if_error(tokenizer_init(&self->tokenizer, self->allocator, code_buffer, buffer_name, &self->compiler->options)); result = setjmp(self->parse_env); if(result == 0) parser_parse_body_loop(self, &self->struct_decl.body, TOK_END_OF_FILE); @@ -633,7 +634,7 @@ void parser_queue_file(Parser *self, BufferView path, FileScopeReference **file_ file_directory = file_get_parent_directory(self->tokenizer.code_name); throw_if_error(file_path_join(file_directory, path, &path_relative)); /* We want buffer to be null-terminated but null character should not be included for the size */ - result = amal_compiler_load_file(self->compiler, path_relative, file_scope); + result = amal_compiler_internal_load_file(self->compiler, path_relative, file_scope); if(result != 0) { self->error = tokenizer_create_error(&self->tokenizer, tokenizer_get_code_reference_index(&self->tokenizer, path.data), diff --git a/src/std/buffer.c b/src/std/buffer.c index e631153..8e23a30 100644 --- a/src/std/buffer.c +++ b/src/std/buffer.c @@ -62,6 +62,10 @@ int buffer_pop(Buffer *self, void *data, usize size) { return 0; } +void buffer_clear(Buffer *self) { + self->size = 0; +} + void* buffer_start(Buffer *self) { return self->data; } diff --git a/src/tokenizer.c b/src/tokenizer.c index d873b0e..7f6d08e 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -25,8 +25,9 @@ static int tokenizer_get_end_of_line_from_index(Tokenizer *self, int index); /* Returns -1 if end of multiline comment was not found */ static int tokenizer_get_end_of_multiline_comment(Tokenizer *self, int index); -int tokenizer_init(Tokenizer *self, ScopedAllocator *allocator, BufferView code, BufferView code_name) { +int tokenizer_init(Tokenizer *self, ScopedAllocator *allocator, BufferView code, BufferView code_name, const amal_compiler_options *compiler_options) { assert(code.size <= INT_MAX); + assert(compiler_options); self->code = code; self->index = 0; self->prev_index = 0; @@ -35,6 +36,7 @@ int tokenizer_init(Tokenizer *self, ScopedAllocator *allocator, BufferView code, self->code_name = code_name.data ? code_name : create_buffer_view("<buffer>", 8); self->number_is_integer = bool_false; self->allocator = allocator; + self->compiler_options = compiler_options; return 0; } @@ -569,7 +571,18 @@ int tokenizer_get_end_of_line_from_index(Tokenizer *self, int index) { } return index; } - +/* +static int find_non_whitespace(const char *str, usize size) { + usize i; + for(i = 0; i < size; ++i) { + char c; + c = str[i]; + if(c != ' ' && c != '\t') + return i; + } + return -1; +} +*/ int tokenizer_get_end_of_multiline_comment(Tokenizer *self, int index) { char c; int comment_count; @@ -608,30 +621,66 @@ static int tokenizer_get_line_by_index(Tokenizer *self, int index) { return line; } +static int max(int a, int b) { + return a > b ? a : b; +} + void tokenizer_print_error(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; - amal_mutex *mutex; - mutex = amal_log_get_mutex(); - ignore_result_int(amal_mutex_lock(mutex, "tokenizer_print_error")); - va_start(args, fmt); 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; - 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"); - va_end(args); - ignore_result_int(amal_mutex_unlock(mutex)); + + 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) { + va_start(args, fmt); + bytes_copied += max(0, vsnprintf(buffer + bytes_copied, sizeof(buffer) - bytes_copied, fmt, args)); + va_end(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")); + va_start(args, fmt); + 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"); + va_end(args); + ignore_result_int(amal_mutex_unlock(mutex)); + } } void tokenizer_print_error_object(Tokenizer *self, TokenizerError *error) { @@ -645,10 +694,8 @@ TokenizerError tokenizer_create_error(Tokenizer *self, int index, const char *fm int bytes_copied; va_start(args, fmt); - bytes_copied = vsnprintf(buffer, sizeof(buffer), fmt, args); + bytes_copied = max(0, vsnprintf(buffer, sizeof(buffer), fmt, args)); va_end(args); - if(bytes_copied < 0) - bytes_copied = 0; result.index = index; result.str = NULL; diff --git a/tests/errors/closure_no_lhs.amal b/tests/errors/closure_no_lhs.amal new file mode 100644 index 0000000..d903c3c --- /dev/null +++ b/tests/errors/closure_no_lhs.amal @@ -0,0 +1 @@ +fn {}
\ No newline at end of file diff --git a/tests/errors/duplicate_declaration.amal b/tests/errors/duplicate_declaration.amal new file mode 100644 index 0000000..a97b507 --- /dev/null +++ b/tests/errors/duplicate_declaration.amal @@ -0,0 +1,2 @@ +const main = fn {} +const main = fn {}
\ No newline at end of file diff --git a/tests/errors/pub_in_closure.amal b/tests/errors/pub_in_closure.amal new file mode 100644 index 0000000..881bbd4 --- /dev/null +++ b/tests/errors/pub_in_closure.amal @@ -0,0 +1,3 @@ +const main = fn { + pub const num = 45; +}
\ No newline at end of file diff --git a/tests/main.amal b/tests/main.amal index f778a14..ae37816 100644 --- a/tests/main.amal +++ b/tests/main.amal @@ -10,15 +10,12 @@ const main = fn { } const value = "hello"; - // fn {} // error, function declaration can't be by itself. Needs left-hand side print(value, "world", 356, 13.37); var num1: i64; const num2 = 23232; const num3 = num1 + num2 * 30; //const num4 = (num1 + num2) * num3 * ((34 + 32) / 234.345); const num4 = (num1 + num2) * num3 * ((34 + 32) / 2); - // pub cost num34 = 45; // error, only declarations in global scope can be public - //const num4 = 23; // error, variable redeclaration /* episfjpseifipesf */ diff --git a/tests/main.c b/tests/main.c index 0dfd878..bc2fbb1 100644 --- a/tests/main.c +++ b/tests/main.c @@ -5,6 +5,7 @@ #include "../include/std/hash.h" #include <stdio.h> #include <stdlib.h> +#include <unistd.h> #define REQUIRE_EQ_INT(expected, actual) do{\ if((expected) != (actual)) {\ @@ -41,25 +42,141 @@ static CHECK_RESULT int test_hash_map() { return 0; } -/* TODO: Restrict variables in global scope to const */ -int main() { +#define FAIL_TEST(name) do { fprintf(stderr, "Test failed: %s\n", (name)); exit(1); } while(0) + +typedef struct { + char *filepath; + char *expected_error; + bool got_expected_error; +} ErrorExpectedData; + +static void error_callback_assert(const char *err_msg, int err_msg_len, void *userdata) { + ErrorExpectedData *expected_data; + expected_data = userdata; + int expected_err_msg_len; + expected_err_msg_len = strlen(expected_data->expected_error); + + if(expected_data->got_expected_error) { + fprintf(stderr, "We got the error we expected but also got additional error:\n%.*s\n", err_msg_len, err_msg); + FAIL_TEST(expected_data->filepath); + } + + if(err_msg_len != expected_err_msg_len || strncmp(err_msg, expected_data->expected_error, expected_err_msg_len) != 0) { + fprintf(stderr, "Expected error message:\n%.*s\n", expected_err_msg_len, expected_data->expected_error); + fprintf(stderr, "Actual error message:\n%.*s\n", err_msg_len, err_msg); + fprintf(stderr, "a: %d, b: %d\n", expected_err_msg_len, err_msg_len); + FAIL_TEST(expected_data->filepath); + } + + expected_data->got_expected_error = bool_true; +} + +static char* get_full_path(const char *filepath) { + #define PATH_LEN 4096 + char *buf; + int len; + int filepath_len; + + buf = malloc(PATH_LEN); + buf[PATH_LEN - 1] = '\0'; + getcwd(buf, PATH_LEN); + + len = strlen(buf); + filepath_len = strlen(filepath); + + buf[len++] = '/'; + memcpy(buf + len, filepath, filepath_len); + buf[len + filepath_len] = '\0'; + return buf; +} + +static char* join_str(const char *str1, const char *str2, char delimiter) { + char *buf; + int len1; + int len2; + + len1 = strlen(str1); + len2 = strlen(str2); + buf = malloc(len1 + 1 + len2 + 1); + + memcpy(buf, str1, len1); + buf[len1] = delimiter; + memcpy(buf + len1 + 1, str2, len2); + buf[len1 + 1 + len2] = '\0'; + return buf; +} + +static void test_load_error(const char *filepath, const char *expected_error) { amal_compiler compiler; - FileScopeReference *file_scope; + amal_compiler_options options; int result; + amal_compiler_options_init(&options); + options.error_callback = error_callback_assert; + ErrorExpectedData expected_data; + expected_data.filepath = get_full_path(filepath); + expected_data.expected_error = join_str(expected_data.filepath, expected_error, ':'); + expected_data.got_expected_error = bool_false; + options.error_callback_userdata = &expected_data; + + result = amal_compiler_init(&compiler, &options); + if(result != AMAL_COMPILER_OK) { + fprintf(stderr, "Failed to initialize compiler, error code: %d\n", result); + FAIL_TEST(expected_data.filepath); + } + + result = amal_compiler_load_file(&compiler, filepath); + if(result == AMAL_COMPILER_OK) { + fprintf(stderr, "Expected to fail loading file\n"); + FAIL_TEST(expected_data.filepath); + } + + if(amal_compiler_deinit(&compiler) != 0) { + fprintf(stderr, "Failed to deinitialize compiler.\n"); + FAIL_TEST(expected_data.filepath); + } + + if(!expected_data.got_expected_error) { + fprintf(stderr, "Didn't get expected error message:\n%s\n", expected_error); + FAIL_TEST(expected_data.filepath); + } + free(expected_data.filepath); + free(expected_data.expected_error); +} + +/* TODO: Restrict variables in global scope to const */ +int main() { + /*amal_compiler compiler; + int result;*/ + return_if_error(test_hash_map()); - result = amal_compiler_init(&compiler); + /* + result = amal_compiler_init(&compiler, NULL); if(result != AMAL_COMPILER_OK) { fprintf(stderr, "Failed to initialize compiler, error code: %d\n", result); return 1; } - result = amal_compiler_load_file(&compiler, "tests/main.amal", &file_scope); + result = amal_compiler_load_file(&compiler, "tests/main.amal"); if(result != AMAL_COMPILER_OK) { fprintf(stderr, "Failed to load file, error code: %d\n", result); return 1; } return amal_compiler_deinit(&compiler); + */ + test_load_error("tests/errors/duplicate_declaration.amal", + "2:7: error: Variable with the name main was declared twice in the same scope\n" + "const main = fn {}\n" + " ^\n"); + test_load_error("tests/errors/pub_in_closure.amal", + "2:5: error: Only declarations in structs can be public\n" + " pub const num = 45;\n" + " ^\n"); + test_load_error("tests/errors/closure_no_lhs.amal", + "1:1: error: Expected variable declaration, string, variable or function call\n" + "fn {}\n" + "^\n"); + return 0; } |