aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/compiler.h12
-rw-r--r--include/compiler_options.h11
-rw-r--r--include/parser.h1
-rw-r--r--include/std/buffer.h2
-rw-r--r--include/tokenizer.h4
-rw-r--r--src/compiler.c94
-rw-r--r--src/parser.c5
-rw-r--r--src/std/buffer.c4
-rw-r--r--src/tokenizer.c81
-rw-r--r--tests/errors/closure_no_lhs.amal1
-rw-r--r--tests/errors/duplicate_declaration.amal2
-rw-r--r--tests/errors/pub_in_closure.amal3
-rw-r--r--tests/main.amal3
-rw-r--r--tests/main.c127
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;
}