소스 검색

Filename as default import name; as .; as _; panic()

Ginger Bill 9 년 전
부모
커밋
0e2347e582
14개의 변경된 파일314개의 추가작업 그리고 168개의 파일을 삭제
  1. 0 3
      build.bat
  2. 3 2
      code/demo.odin
  3. 3 3
      code/game.odin
  4. 3 3
      code/punity.odin
  5. 1 1
      code/test.odin
  6. 1 1
      core/fmt.odin
  7. 1 2
      core/os.odin
  8. 99 89
      core/runtime.odin
  9. 53 9
      src/checker/checker.cpp
  10. 17 1
      src/checker/expr.cpp
  11. 30 19
      src/codegen/ssa.cpp
  12. 32 19
      src/main.cpp
  13. 46 16
      src/parser.cpp
  14. 25 0
      src/unicode.cpp

+ 0 - 3
build.bat

@@ -46,9 +46,6 @@ rem pushd %build_dir%
 	del *.pdb > NUL 2> NUL
 	del *.ilk > NUL 2> NUL
 
-	del ..\misc\*.pdb > NUL 2> NUL
-	del ..\misc\*.ilk > NUL 2> NUL
-
 	cl %compiler_settings% "src\main.cpp" ^
 		/link %linker_settings% -OUT:%exe_name% ^
 	&& odin run code/demo.odin

+ 3 - 2
code/demo.odin

@@ -1,5 +1,6 @@
-#import "fmt.odin" as fmt
-#import "os.odin" as os
+#import "fmt.odin"
+#import "os.odin"
+
 
 main :: proc() {
 	Fruit :: enum {

+ 3 - 3
code/game.odin

@@ -1,7 +1,7 @@
-#import "win32.odin"  as win32
-#import "fmt.odin"    as fmt
+#import "win32.odin"
+#import "fmt.odin"
+#import "math.odin"
 #import "opengl.odin" as gl
-#import "math.odin"   as math
 
 TWO_HEARTS :: #rune "💕"
 

+ 3 - 3
code/punity.odin

@@ -1,6 +1,6 @@
-#import "win32.odin" as win32
-#import "fmt.odin"   as fmt
-#import "os.odin"    as os
+#import "win32.odin"
+#import "fmt.odin"
+#import "os.odin"
 
 CANVAS_WIDTH  :: 128
 CANVAS_HEIGHT :: 128

+ 1 - 1
code/test.odin

@@ -1,4 +1,4 @@
-#import "fmt.odin" as fmt
+#import "fmt.odin"
 
 thing :: proc() #link_name "frankerooney" {
 	fmt.println("Hello!")

+ 1 - 1
core/fmt.odin

@@ -1,4 +1,4 @@
-#import "os.odin" as os
+#import "os.odin"
 
 PRINT_BUF_SIZE :: 1<<12
 

+ 1 - 2
core/os.odin

@@ -1,4 +1,4 @@
-#import "win32.odin" as win32
+#import "win32.odin"
 
 File :: type struct {
 	Handle :: type win32.HANDLE
@@ -50,7 +50,6 @@ stdin  := ^__std_files[File_Standard.INPUT]
 stdout := ^__std_files[File_Standard.OUTPUT]
 stderr := ^__std_files[File_Standard.ERROR]
 
-
 __set_file_standards :: proc() -> [File_Standard.COUNT as int]File {
 	return [File_Standard.COUNT as int]File{
 		File{handle = win32.GetStdHandle(win32.STD_INPUT_HANDLE)},

+ 99 - 89
core/runtime.odin

@@ -1,7 +1,7 @@
 #shared_global_scope
 
-#import "os.odin"  as os
-#import "fmt.odin" as fmt
+#import "os.odin"
+#import "fmt.odin"
 
 // IMPORTANT NOTE(bill): Do not change the order of any of this data
 // The compiler relies upon this _exact_ order
@@ -134,93 +134,6 @@ memory_copy :: proc(dst, src: rawptr, len: int) #inline {
 	llvm_memmove_64bit(dst, src, len, 1, false)
 }
 
-__string_eq :: proc(a, b: string) -> bool {
-	if a.count != b.count {
-		return false
-	}
-	if ^a[0] == ^b[0] {
-		return true
-	}
-	return memory_compare(^a[0], ^b[0], a.count) == 0
-}
-
-__string_cmp :: proc(a, b : string) -> int {
-	// Translation of http://mgronhol.github.io/fast-strcmp/
-	n := min(a.count, b.count)
-
-	fast := n/size_of(int) + 1
-	offset := (fast-1)*size_of(int)
-	curr_block := 0
-	if n <= size_of(int) {
-		fast = 0
-	}
-
-	la := slice_ptr(^a[0] as ^int, fast)
-	lb := slice_ptr(^b[0] as ^int, fast)
-
-	for ; curr_block < fast; curr_block++ {
-		if (la[curr_block] ~ lb[curr_block]) != 0 {
-			for pos := curr_block*size_of(int); pos < n; pos++ {
-				if (a[pos] ~ b[pos]) != 0 {
-					return a[pos] as int - b[pos] as int
-				}
-			}
-		}
-
-	}
-
-	for ; offset < n; offset++ {
-		if (a[offset] ~ b[offset]) != 0 {
-			return a[offset] as int - b[offset] as int
-		}
-	}
-
-	return 0
-}
-
-__string_ne :: proc(a, b : string) -> bool #inline { return !__string_eq(a, b) }
-__string_lt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) < 0 }
-__string_gt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) > 0 }
-__string_le :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) <= 0 }
-__string_ge :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) >= 0 }
-
-
-__assert :: proc(msg: string) {
-	fmt.fprintln(os.stderr, msg)
-	__debug_trap()
-}
-
-__bounds_check_error :: proc(file: string, line, column: int,
-                             index, count: int) {
-	if 0 <= index && index < count {
-		return
-	}
-	fmt.fprintf(os.stderr, "%(%:%) Index % is out of bounds range [0, %)\n",
-	            file, line, column, index, count)
-	__debug_trap()
-}
-
-__slice_expr_error :: proc(file: string, line, column: int,
-                           low, high, max: int) {
-	if 0 <= low && low <= high && high <= max {
-		return
-	}
-	fmt.fprintf(os.stderr, "%(%:%) Invalid slice indices: [%:%:%]\n",
-	            file, line, column, low, high, max)
-	__debug_trap()
-}
-__substring_expr_error :: proc(file: string, line, column: int,
-                               low, high: int) {
-	if 0 <= low && low <= high {
-		return
-	}
-	fmt.fprintf(os.stderr, "%(%:%) Invalid substring indices: [%:%:%]\n",
-	            file, line, column, low, high)
-	__debug_trap()
-}
-
-
-
 
 
 
@@ -357,6 +270,103 @@ __default_allocator :: proc() -> Allocator {
 }
 
 
+
+
+
+
+
+
+
+
+
+
+
+__string_eq :: proc(a, b: string) -> bool {
+	if a.count != b.count {
+		return false
+	}
+	if ^a[0] == ^b[0] {
+		return true
+	}
+	return memory_compare(^a[0], ^b[0], a.count) == 0
+}
+
+__string_cmp :: proc(a, b : string) -> int {
+	// Translation of http://mgronhol.github.io/fast-strcmp/
+	n := min(a.count, b.count)
+
+	fast := n/size_of(int) + 1
+	offset := (fast-1)*size_of(int)
+	curr_block := 0
+	if n <= size_of(int) {
+		fast = 0
+	}
+
+	la := slice_ptr(^a[0] as ^int, fast)
+	lb := slice_ptr(^b[0] as ^int, fast)
+
+	for ; curr_block < fast; curr_block++ {
+		if (la[curr_block] ~ lb[curr_block]) != 0 {
+			for pos := curr_block*size_of(int); pos < n; pos++ {
+				if (a[pos] ~ b[pos]) != 0 {
+					return a[pos] as int - b[pos] as int
+				}
+			}
+		}
+
+	}
+
+	for ; offset < n; offset++ {
+		if (a[offset] ~ b[offset]) != 0 {
+			return a[offset] as int - b[offset] as int
+		}
+	}
+
+	return 0
+}
+
+__string_ne :: proc(a, b : string) -> bool #inline { return !__string_eq(a, b) }
+__string_lt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) < 0 }
+__string_gt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) > 0 }
+__string_le :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) <= 0 }
+__string_ge :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) >= 0 }
+
+
+__assert :: proc(file: string, line, column: int, msg: string) #inline {
+	fmt.fprintf(os.stderr, "%(%:%) Runtime assertion: %\n",
+	            file, line, column, msg)
+	__debug_trap()
+}
+
+__bounds_check_error :: proc(file: string, line, column: int,
+                             index, count: int) {
+	if 0 <= index && index < count {
+		return
+	}
+	fmt.fprintf(os.stderr, "%(%:%) Index % is out of bounds range [0, %)\n",
+	            file, line, column, index, count)
+	__debug_trap()
+}
+
+__slice_expr_error :: proc(file: string, line, column: int,
+                           low, high, max: int) {
+	if 0 <= low && low <= high && high <= max {
+		return
+	}
+	fmt.fprintf(os.stderr, "%(%:%) Invalid slice indices: [%:%:%]\n",
+	            file, line, column, low, high, max)
+	__debug_trap()
+}
+__substring_expr_error :: proc(file: string, line, column: int,
+                               low, high: int) {
+	if 0 <= low && low <= high {
+		return
+	}
+	fmt.fprintf(os.stderr, "%(%:%) Invalid substring indices: [%:%:%]\n",
+	            file, line, column, low, high)
+	__debug_trap()
+}
+
 __enum_to_string :: proc(info: ^Type_Info, value: i64) -> string {
 	info = type_info_base(info)
 

+ 53 - 9
src/checker/checker.cpp

@@ -145,6 +145,7 @@ enum BuiltinProcId {
 
 	BuiltinProc_compile_assert,
 	BuiltinProc_assert,
+	BuiltinProc_panic,
 
 	BuiltinProc_copy,
 	BuiltinProc_append,
@@ -188,6 +189,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_Count] = {
 
 	{STR_LIT("compile_assert"),   1, false, Expr_Stmt},
 	{STR_LIT("assert"),           1, false, Expr_Stmt},
+	{STR_LIT("panic"),            1, false, Expr_Stmt},
 
 	{STR_LIT("copy"),             2, false, Expr_Expr},
 	{STR_LIT("append"),           2, false, Expr_Expr},
@@ -416,12 +418,15 @@ Entity *scope_insert_entity(Scope *s, Entity *entity) {
 
 void check_scope_usage(Checker *c, Scope *scope) {
 	// TODO(bill): Use this?
-#if 0
+#if 1
 	gb_for_array(i, scope->elements.entries) {
 		auto *entry = scope->elements.entries + i;
 		Entity *e = entry->value;
-		if (e->kind == Entity_Variable && !e->Variable.used) {
-			warning(e->token, "Unused variable: %.*s", LIT(e->token.string));
+		if (e->kind == Entity_Variable) {
+			auto *v = &e->Variable;
+			if (!v->is_field && !v->used) {
+				warning(e->token, "Unused variable: %.*s", LIT(e->token.string));
+			}
 		}
 	}
 
@@ -1040,7 +1045,7 @@ void check_parsed_files(Checker *c) {
 				warning(id->token, "Multiple #import of the same file within this scope");
 			}
 
-			if (id->import_name.string == make_string("_")) {
+			if (id->import_name.string == make_string(".")) {
 				// NOTE(bill): Add imported entities to this file's scope
 				gb_for_array(elem_index, scope->elements.entries) {
 					Entity *e = scope->elements.entries[elem_index].value;
@@ -1057,11 +1062,50 @@ void check_parsed_files(Checker *c) {
 					}
 				}
 			} else {
-				GB_ASSERT(id->import_name.string.len > 0);
-				Entity *e = make_entity_import_name(c->allocator, file_scope, id->import_name, t_invalid,
-				                                    id->fullpath, id->import_name.string,
-				                                    scope);
-				add_entity(c, file_scope, NULL, e);
+				String import_name = id->import_name.string;
+				if (import_name.len == 0) {
+					// NOTE(bill): use file name (without extension) as the identifier
+					// If it is a valid identifier
+					String filename = id->fullpath;
+					isize slash = 0;
+					isize dot = 0;
+					for (isize i = filename.len-1; i >= 0; i--) {
+						u8 c = filename.text[i];
+						if (c == '/' || c == '\\') {
+							break;
+						}
+						slash = i;
+					}
+
+					filename.text += slash;
+					filename.len -= slash;
+
+					dot = filename.len;
+					while (dot --> 0) {
+						u8 c = filename.text[dot];
+						if (c == '.') {
+							break;
+						}
+					}
+
+					filename.len = dot;
+
+					if (is_string_an_identifier(filename)) {
+						import_name = filename;
+					} else {
+						error(ast_node_token(decl),
+						      "File name, %.*s, cannot be as an import name as it is not a valid identifier",
+						      LIT(filename));
+					}
+				}
+
+				if (import_name.len > 0) {
+					id->import_name.string = import_name;
+					Entity *e = make_entity_import_name(c->allocator, file_scope, id->import_name, t_invalid,
+					                                    id->fullpath, id->import_name.string,
+					                                    scope);
+					add_entity(c, file_scope, NULL, e);
+				}
 			}
 		}
 	}

+ 17 - 1
src/checker/expr.cpp

@@ -1534,7 +1534,9 @@ b32 check_is_castable_to(Checker *c, Operand *operand, Type *y) {
 		return true;
 	}
 	if (is_type_string(xb) && is_type_u8_slice(yb)) {
-		return true;
+		if (is_type_typed(xb)) {
+			return true;
+		}
 	}
 
 	// proc <-> proc
@@ -2422,6 +2424,20 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id)
 		}
 		break;
 
+	case BuiltinProc_panic:
+		// panic :: proc(msg: string)
+
+		if (!is_type_string(operand->type)) {
+			gbString str = expr_to_string(ce->args[0]);
+			defer (gb_string_free(str));
+			error(ast_node_token(call),
+			      "`%s` is not a string", str);
+			return false;
+		}
+
+		operand->mode = Addressing_NoValue;
+		break;
+
 	case BuiltinProc_copy: {
 		// copy :: proc(x, y: []Type) -> int
 		Type *dest_type = NULL, *src_type = NULL;

+ 30 - 19
src/codegen/ssa.cpp

@@ -2230,38 +2230,49 @@ ssaValue *ssa_build_single_expr(ssaProcedure *proc, AstNode *expr, TypeAndValue
 					ssa_emit_if(proc, cond, err, done);
 					proc->curr_block = err;
 
+					// TODO(bill): Cleanup allocations here
 					Token token = ast_node_token(ce->args[0]);
 					TokenPos pos = token.pos;
 					gbString expr = expr_to_string(ce->args[0]);
 					defer (gb_string_free(expr));
+					isize expr_len = gb_string_length(expr);
+					String expr_str = {};
+					expr_str.text = cast(u8 *)gb_alloc_copy_align(proc->module->allocator, expr, expr_len, 1);
+					expr_str.len = expr_len;
+
+					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 4);
+					args[0] = ssa_emit_global_string(proc, pos.file);
+					args[1] = ssa_make_const_int(proc->module->allocator, pos.line);
+					args[2] = ssa_make_const_int(proc->module->allocator, pos.column);
+					args[3] = ssa_emit_global_string(proc, expr_str);
+					ssa_emit_global_call(proc, "__assert", args, 4);
 
-					isize err_len = pos.file.len + 1 + 10 + 1 + 10 + 1;
-					err_len += 20;
-					err_len += gb_string_length(expr);
-					err_len += 2;
+					ssa_emit_jump(proc, done);
+					gb_array_append(proc->blocks, done);
+					proc->curr_block = done;
 
-					u8 *err_str = gb_alloc_array(proc->module->allocator, u8, err_len);
-					err_len = gb_snprintf(cast(char *)err_str, err_len,
-					                      "%.*s(%td:%td) Runtime assertion: %s\n",
-					                      LIT(pos.file), pos.line, pos.column, expr);
-					err_len--;
+					return NULL;
+				} break;
 
-					ssaValue *array = ssa_add_global_string_array(proc->module, make_string(err_str, err_len));
-					ssaValue *elem = ssa_array_elem(proc, array);
-					ssaValue *len = ssa_make_const_int(proc->module->allocator, err_len);
-					ssaValue *string = ssa_emit_string(proc, elem, len);
+				case BuiltinProc_panic: {
+					ssa_emit_comment(proc, make_string("panic"));
+					ssaValue *msg = ssa_build_expr(proc, ce->args[0]);
+					GB_ASSERT(is_type_string(ssa_type(msg)));
 
-					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 1);
-					args[0] = string;
-					ssa_emit_global_call(proc, "__assert", args, 1);
+					Token token = ast_node_token(ce->args[0]);
+					TokenPos pos = token.pos;
 
-					ssa_emit_jump(proc, done);
-					gb_array_append(proc->blocks, done);
-					proc->curr_block = done;
+					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 4);
+					args[0] = ssa_emit_global_string(proc, pos.file);
+					args[1] = ssa_make_const_int(proc->module->allocator, pos.line);
+					args[2] = ssa_make_const_int(proc->module->allocator, pos.column);
+					args[3] = msg;
+					ssa_emit_global_call(proc, "__assert", args, 4);
 
 					return NULL;
 				} break;
 
+
 				case BuiltinProc_copy: {
 					ssa_emit_comment(proc, make_string("copy"));
 					// copy :: proc(dst, src: []Type) -> int

+ 32 - 19
src/main.cpp

@@ -126,33 +126,46 @@ int main(int argc, char **argv) {
 	isize base_name_len = gb_path_extension(output_name)-1 - output_name;
 
 
-
 	i32 exit_code = 0;
-	// For more passes arguments: http://llvm.org/docs/Passes.html
-	exit_code = win32_exec_command_line_app(
-		// "../misc/llvm-bin/opt %s -o %.*s.bc "
-		"opt %s -o %.*s.bc "
-		"-memcpyopt "
-		"-mem2reg "
-		"-die -dse "
-		"-dce "
-		// "-S "
-		// "-debug-pass=Arguments "
-		"",
-		output_name, cast(int)base_name_len, output_name);
-	if (exit_code != 0)
-		return exit_code;
-
-	PRINT_TIMER("llvm-opt");
+	{
+		char buf[300] = {};
+		u32 buf_len = GetModuleFileNameA(GetModuleHandleA(NULL), buf, gb_size_of(buf));
+		for (isize i = buf_len-1; i >= 0; i--) {
+			if (buf[i] == '\\' ||
+			    buf[i] == '/') {
+				break;
+			}
+			buf_len--;
+		}
+
+		// For more passes arguments: http://llvm.org/docs/Passes.html
+		exit_code = win32_exec_command_line_app(
+			// "../misc/llvm-bin/opt %s -o %.*s.bc "
+			"\"%.*sbin\\opt.exe\" %s -o %.*s.bc "
+			"-memcpyopt "
+			"-mem2reg "
+			"-die -dse "
+			"-dce "
+			// "-S "
+			// "-debug-pass=Arguments "
+			"",
+			buf_len, buf,
+			output_name,
+			cast(int)base_name_len, output_name);
+		if (exit_code != 0)
+			return exit_code;
+
+		PRINT_TIMER("llvm-opt");
+	}
 
 #if 1
-	gbString lib_str = gb_string_make(gb_heap_allocator(), "-lKernel32.lib");
+	gbString lib_str = gb_string_make(gb_heap_allocator(), "-lKernel32");
 	// defer (gb_string_free(lib_str));
 	char lib_str_buf[1024] = {};
 	gb_for_array(i, parser.system_libraries) {
 		String lib = parser.system_libraries[i];
 		isize len = gb_snprintf(lib_str_buf, gb_size_of(lib_str_buf),
-		                        " -l%.*s.lib", LIT(lib));
+		                        " -l%.*s", LIT(lib));
 		lib_str = gb_string_appendc(lib_str, lib_str_buf);
 	}
 

+ 46 - 16
src/parser.cpp

@@ -2587,10 +2587,18 @@ AstNode *parse_stmt(AstFile *f) {
 			return make_bad_decl(f, token, f->cursor[0]);
 		} else if (tag == make_string("import")) {
 			// TODO(bill): better error messages
-			Token import_name;
+			Token import_name = {};
 			Token file_path = expect_token(f, Token_String);
-			expect_token(f, Token_as);
-			import_name = expect_token(f, Token_Identifier);
+			if (allow_token(f, Token_as)) {
+				// NOTE(bill): Custom import name
+				if (f->cursor[0].kind == Token_Period) {
+					import_name = f->cursor[0];
+					import_name.kind = Token_Identifier;
+					next_token(f);
+				} else {
+					import_name = expect_token(f, Token_Identifier);
+				}
+			}
 
 			if (f->curr_proc == NULL) {
 				return make_import_decl(f, s->TagStmt.token, file_path, import_name, false);
@@ -2601,7 +2609,7 @@ AstNode *parse_stmt(AstFile *f) {
 			// TODO(bill): better error messages
 			Token file_path = expect_token(f, Token_String);
 			Token import_name = file_path;
-			import_name.string = make_string("_");
+			import_name.string = make_string(".");
 
 			if (f->curr_proc == NULL) {
 				return make_import_decl(f, s->TagStmt.token, file_path, import_name, true);
@@ -2683,7 +2691,7 @@ AstNodeArray parse_stmt_list(AstFile *f) {
 
 ParseFileError init_ast_file(AstFile *f, String fullpath) {
 	if (!string_has_extension(fullpath, make_string("odin"))) {
-		gb_printf_err("Only `.odin` files are allowed\n");
+		// gb_printf_err("Only `.odin` files are allowed\n");
 		return ParseFile_WrongExtension;
 	}
 	TokenizerInitError err = init_tokenizer(&f->tokenizer, fullpath);
@@ -2775,11 +2783,13 @@ b32 try_add_import_path(Parser *p, String path, String rel_path, TokenPos pos) {
 
 String get_fullpath_relative(gbAllocator a, String base_dir, String path) {
 	isize str_len = base_dir.len+path.len;
+
 	u8 *str = gb_alloc_array(gb_heap_allocator(), u8, str_len+1);
 	defer (gb_free(gb_heap_allocator(), str));
 
-	gb_memcopy(str, base_dir.text, base_dir.len);
-	gb_memcopy(str+base_dir.len, path.text, path.len);
+	isize i = 0;
+	gb_memcopy(str+i, base_dir.text, base_dir.len); i += base_dir.len;
+	gb_memcopy(str+i, path.text, path.len);
 	str[str_len] = '\0';
 	char *path_str = gb_path_get_full_name(a, cast(char *)str);
 	return make_string(path_str);
@@ -2861,6 +2871,26 @@ b32 is_import_path_valid(String path) {
 	return false;
 }
 
+String get_filepath_extension(String path) {
+	isize dot = 0;
+	b32 seen_slash = false;
+	for (isize i = path.len-1; i >= 0; i--) {
+		u8 c = path.text[i];
+		if (c == '/' || c == '\\') {
+			seen_slash = true;
+		}
+
+		if (c == '.') {
+			if (seen_slash) {
+				return make_string("");
+			}
+
+			dot = i;
+			break;
+		}
+	}
+	return make_string(path.text, dot);
+}
 
 void parse_file(Parser *p, AstFile *f) {
 	String filepath = f->tokenizer.fullpath;
@@ -2900,9 +2930,8 @@ void parse_file(Parser *p, AstFile *f) {
 					continue;
 				}
 
-				String import_file = {};
 				String rel_path = get_fullpath_relative(allocator, base_dir, file_str);
-				import_file = rel_path;
+				String import_file = rel_path;
 				if (!gb_file_exists(cast(char *)rel_path.text)) { // NOTE(bill): This should be null terminated
 					String abs_path = get_fullpath_core(allocator, file_str);
 					if (gb_file_exists(cast(char *)abs_path.text)) {
@@ -2956,25 +2985,26 @@ ParseFileError parse_files(Parser *p, char *init_filename) {
 			if (pos.line != 0) {
 				gb_printf_err("%.*s(%td:%td) ", LIT(pos.file), pos.line, pos.column);
 			}
-			gb_printf_err("Failed to parse file: %.*s\n", LIT(import_rel_path));
+			gb_printf_err("Failed to parse file: %.*s\n\t", LIT(import_rel_path));
+			defer (gb_printf_err("\n"));
 			switch (err) {
 			case ParseFile_WrongExtension:
-				gb_printf_err("\tInvalid file extension\n");
+				gb_printf_err("Invalid file extension: File must have the extension `.odin`");
 				break;
 			case ParseFile_InvalidFile:
-				gb_printf_err("\tInvalid file\n");
+				gb_printf_err("Invalid file");
 				break;
 			case ParseFile_EmptyFile:
-				gb_printf_err("\tFile is empty\n");
+				gb_printf_err("File is empty");
 				break;
 			case ParseFile_Permission:
-				gb_printf_err("\tFile permissions problem\n");
+				gb_printf_err("File permissions problem");
 				break;
 			case ParseFile_NotFound:
-				gb_printf_err("\tFile cannot be found\n");
+				gb_printf_err("File cannot be found");
 				break;
 			case ParseFile_InvalidToken:
-				gb_printf_err("\tInvalid token found in file\n");
+				gb_printf_err("Invalid token found in file");
 				break;
 			}
 			return err;

+ 25 - 0
src/unicode.cpp

@@ -39,3 +39,28 @@ b32 rune_is_whitespace(Rune r) {
 	}
 	return false;
 }
+
+
+b32 is_string_an_identifier(String s) {
+	if (s.len < 1) {
+		return false;
+	}
+	isize offset = 0;
+	while (offset < s.len) {
+		b32 ok = false;
+		Rune r = -1;
+		isize size = gb_utf8_decode(s.text+offset, s.len-offset, &r);
+		if (offset == 0) {
+			ok = rune_is_letter(r);
+		} else {
+			ok = rune_is_letter(r) || rune_is_digit(r);
+		}
+
+		if (!ok) {
+			return false;
+		}
+		offset += size;
+	}
+
+	return offset == s.len;
+}