diff --git a/self_hosted/ast.jou b/self_hosted/ast.jou index 7dbf5fe5..d829a084 100644 --- a/self_hosted/ast.jou +++ b/self_hosted/ast.jou @@ -709,9 +709,11 @@ class AstFile: # Iterating over imports: # imp: AstImport* = NULL - # while file->next_import(&imp): + # while file->next_import(&imp, NULL): # ... - def next_import(self, imp: AstImport**) -> bool: + # + # If imp_location is given, it is used to store the location of each import statement. + def next_import(self, imp: AstImport**, imp_location: Location*) -> bool: # Get the corresponding AstToplevelStatement. ts = *imp as AstToplevelStatement* assert &ts->the_import as void* == ts # TODO: offsetof() or similar @@ -725,6 +727,8 @@ class AstFile: if ts == &self->body[self->body_len] or ts->kind != AstToplevelStatementKind::Import: return False *imp = &ts->the_import + if imp_location != NULL: + *imp_location = ts->location return True def print(self) -> void: diff --git a/self_hosted/main.jou b/self_hosted/main.jou index aaa52158..441727ba 100644 --- a/self_hosted/main.jou +++ b/self_hosted/main.jou @@ -1,5 +1,6 @@ import "../config.jou" import "./ast.jou" +import "./errors_and_warnings.jou" import "./tokenizer.jou" import "./parser.jou" import "./types.jou" @@ -121,6 +122,11 @@ class FileState: typectx: FileTypes pending_exports: ExportSymbol* +class ParseQueueItem: + path: byte* + is_imported: bool + import_location: Location + class Compiler: argv0: byte* verbosity: int @@ -131,33 +137,37 @@ class Compiler: automagic_files: byte*[10] def determine_automagic_files(self) -> void: - # TODO: this breaks too much stuff - return -# self->automagic_files[0] = malloc(strlen(self->stdlib_path) + 40) -# sprintf(self->automagic_files[0], "%s/_assert_fail.jou", self->stdlib_path) + self->automagic_files[0] = malloc(strlen(self->stdlib_path) + 40) + sprintf(self->automagic_files[0], "%s/_assert_fail.jou", self->stdlib_path) + if is_windows(): + self->automagic_files[1] = malloc(strlen(self->stdlib_path) + 40) + sprintf(self->automagic_files[1], "%s/_windows_startup.jou", self->stdlib_path) def parse_all_files(self) -> void: - queue: byte** = malloc(50 * sizeof queue[0]) + queue: ParseQueueItem* = malloc(50 * sizeof queue[0]) queue_len = 0 - queue[queue_len++] = self->args->main_path + queue[queue_len++] = ParseQueueItem{path = self->args->main_path} for i = 0; self->automagic_files[i] != NULL; i++: - queue[queue_len++] = self->automagic_files[i] + queue[queue_len++] = ParseQueueItem{path = self->automagic_files[i]} while queue_len > 0: - path = queue[--queue_len] + item = queue[--queue_len] found = False for i = 0; i < self->nfiles; i++: - if strcmp(self->files[i].ast.path, path) == 0: + if strcmp(self->files[i].ast.path, item.path) == 0: found = True break if found: continue if self->verbosity >= 1: - printf("Parsing %s\n", path) + printf("Parsing %s\n", item.path) - tokens = tokenize(path) + if item.is_imported: + tokens = tokenize(item.path, &item.import_location) + else: + tokens = tokenize(item.path, NULL) if self->verbosity >= 2: print_tokens(tokens) ast = parse(tokens, self->stdlib_path) @@ -169,9 +179,15 @@ class Compiler: self->files[self->nfiles++] = FileState{ast = ast} imp: AstImport* = NULL - while ast.next_import(&imp): + imp_location: Location + while ast.next_import(&imp, &imp_location): + # TODO: offsetof() queue = realloc(queue, sizeof queue[0] * (queue_len + 1)) - queue[queue_len++] = imp->resolved_path + queue[queue_len++] = ParseQueueItem{ + path = imp->resolved_path, + is_imported = True, + import_location = imp_location, + } free(queue) @@ -185,7 +201,7 @@ class Compiler: dest = &self->files[idest] imp: AstImport* = NULL - while dest->ast.next_import(&imp): + while dest->ast.next_import(&imp, NULL): for exp = src->pending_exports; exp->name[0] != '\0'; exp++: if self->verbosity >= 1: printf( @@ -369,11 +385,11 @@ def main(argc: int, argv: byte**) -> int: args = parse_args(argc, argv) if args.mode == CompilerMode::TokenizeOnly: - tokens = tokenize(args.main_path) + tokens = tokenize(args.main_path, NULL) print_tokens(tokens) free(tokens) elif args.mode == CompilerMode::ParseOnly: - tokens = tokenize(args.main_path) + tokens = tokenize(args.main_path, NULL) stdlib_path = find_stdlib() ast = parse(tokens, stdlib_path) ast.print() diff --git a/self_hosted/runs_wrong.txt b/self_hosted/runs_wrong.txt index 80784970..7f609427 100644 --- a/self_hosted/runs_wrong.txt +++ b/self_hosted/runs_wrong.txt @@ -6,7 +6,6 @@ stdlib/mem.jou stdlib/process.jou stdlib/str.jou stdlib/_windows_startup.jou -tests/404/file.jou tests/404/method_on_class_ptr.jou tests/404/method_on_int.jou tests/already_exists_error/class_import.jou @@ -72,3 +71,5 @@ tests/should_succeed/union.jou tests/other_errors/instantiation_address_of_field.jou tests/other_errors/array_literal_as_a_pointer.jou tests/other_errors/assert_fail_multiline.jou +stdlib/errno.jou +tests/should_succeed/errno_test.jou diff --git a/self_hosted/tokenizer.jou b/self_hosted/tokenizer.jou index afd9a321..914939f4 100644 --- a/self_hosted/tokenizer.jou +++ b/self_hosted/tokenizer.jou @@ -1,6 +1,7 @@ import "stdlib/io.jou" import "stdlib/str.jou" import "stdlib/mem.jou" +import "stdlib/errno.jou" import "./errors_and_warnings.jou" import "./token.jou" @@ -587,12 +588,17 @@ def handle_indentations(raw_tokens: Token*) -> Token*: return tokens -def tokenize(path: byte*) -> Token*: +def tokenize(path: byte*, import_location: Location*) -> Token*: file = fopen(path, "rb") if file == NULL: - # TODO: test this - # TODO: include errno in the message - fail(Location{path=path}, "cannot open file") + message: byte[200] + if import_location == NULL: + # File is not imported + snprintf(message, sizeof message, "cannot open file: %s", strerror(get_errno())) + fail(Location{path=path}, message) + else: + snprintf(message, sizeof message, "cannot import from \"%s\": %s", path, strerror(get_errno())) + fail(*import_location, message) raw_tokens = tokenize_without_indent_dedent_tokens(file, path) better_tokens = handle_indentations(raw_tokens) diff --git a/stdlib/_windows_startup.jou b/stdlib/_windows_startup.jou index 2c65ee4b..7dd4ef8b 100644 --- a/stdlib/_windows_startup.jou +++ b/stdlib/_windows_startup.jou @@ -24,3 +24,9 @@ def _jou_windows_startup() -> void: stdin = __acrt_iob_func(0) stdout = __acrt_iob_func(1) stderr = __acrt_iob_func(2) + +# On linux, C's errno is a macro that expands to (*__errno_location()). +# On Windows it expands to (*_errno()) instead. Let's make it consistent. +declare _errno() -> int* +def __errno_location() -> int*: + return _errno() diff --git a/stdlib/errno.jou b/stdlib/errno.jou new file mode 100644 index 00000000..99afd529 --- /dev/null +++ b/stdlib/errno.jou @@ -0,0 +1,12 @@ +# C's errno is actually a macro that expands to a function call. +# See also _windows_startup.jou +declare __errno_location() -> int* + +def set_errno(value: int) -> void: + *__errno_location() = value + +def get_errno() -> int: + return *__errno_location() + +# Convert an error code into a string. Do not modify or free() the returned string. +declare strerror(errno_value: int) -> byte* diff --git a/tests/should_succeed/errno_test.jou b/tests/should_succeed/errno_test.jou new file mode 100644 index 00000000..a2a1b1cb --- /dev/null +++ b/tests/should_succeed/errno_test.jou @@ -0,0 +1,17 @@ +import "stdlib/io.jou" +import "stdlib/errno.jou" + +def main() -> int: + # Avoid printing strerror(0) because it is platform-specific + printf("%d\n", get_errno()) # Output: 0 + + fopen("this does not exist.txt", "r") + printf("%d %s\n", get_errno(), strerror(get_errno())) # Output: 2 No such file or directory + + set_errno(0) + printf("%d\n", get_errno()) # Output: 0 + + set_errno(2) + printf("%d %s\n", get_errno(), strerror(get_errno())) # Output: 2 No such file or directory + + return 0