From 7ee9051a56ca0c04e6b60f53b9dfe47c75596496 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 27 May 2018 10:49:14 +0100 Subject: [PATCH] IR now builds with the new package system --- build.bat | 2 +- examples/demo/demo.odin | 755 ++++++++++++++++++++++++++++++++++++++++ src/check_decl.cpp | 10 +- src/checker.cpp | 4 + src/ir.cpp | 27 +- src/main.cpp | 4 +- src/parser.cpp | 4 +- src/parser.hpp | 2 + src/string.cpp | 5 +- 9 files changed, 800 insertions(+), 13 deletions(-) create mode 100644 examples/demo/demo.odin diff --git a/build.bat b/build.bat index 91dc44c8f..4eb674c57 100644 --- a/build.bat +++ b/build.bat @@ -42,7 +42,7 @@ del *.ilk > NUL 2> NUL cl %compiler_settings% "src\main.cpp" ^ /link %linker_settings% -OUT:%exe_name% ^ - && odin check examples/demo + && odin run examples/demo del *.obj > NUL 2> NUL diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin new file mode 100644 index 000000000..ee96560d3 --- /dev/null +++ b/examples/demo/demo.odin @@ -0,0 +1,755 @@ +package main + +#assert(_BUFFER_SIZE > 0); + +import "core:fmt" +import "core:strconv" +import "core:mem" +import "core:bits" +import "core:hash" +import "core:math" +import "core:math/rand" +import "core:os" +import "core:raw" +import "core:sort" +import "core:strings" +import "core:types" +import "core:unicode/utf16" +import "core:unicode/utf8" + +import "core:atomics" +import "core:thread" +import "core:sys/win32" + +@(link_name="general_stuff") +general_stuff :: proc() { + fmt.println("# general_stuff"); + { // `do` for inline statements rather than block + foo :: proc() do fmt.println("Foo!"); + if false do foo(); + for false do foo(); + when false do foo(); + + if false do foo(); + else do foo(); + } + + { // Removal of `++` and `--` (again) + x: int; + x += 1; + x -= 1; + } + { // Casting syntaxes + i := i32(137); + ptr := &i; + + _ = (^f32)(ptr); + // ^f32(ptr) == ^(f32(ptr)) + _ = cast(^f32)ptr; + + _ = (^f32)(ptr)^; + _ = (cast(^f32)ptr)^; + + // Questions: Should there be two ways to do it? + } + + /* + * Remove *_val_of built-in procedures + * size_of, align_of, offset_of + * type_of, type_info_of + */ + + { // `expand_to_tuple` built-in procedure + Foo :: struct { + x: int, + b: bool, + } + f := Foo{137, true}; + x, b := expand_to_tuple(f); + fmt.println(f); + fmt.println(x, b); + fmt.println(expand_to_tuple(f)); + } + + { + // .. half-closed range + // ... open range + + for in 0..2 {} // 0, 1 + for in 0...2 {} // 0, 1, 2 + } + + { // Multiple sized booleans + + x0: bool; // default + x1: b8 = true; + x2: b16 = false; + x3: b32 = true; + x4: b64 = false; + + fmt.printf("x1: %T = %v;\n", x1, x1); + fmt.printf("x2: %T = %v;\n", x2, x2); + fmt.printf("x3: %T = %v;\n", x3, x3); + fmt.printf("x4: %T = %v;\n", x4, x4); + + // Having specific sized booleans is very useful when dealing with foreign code + // and to enforce specific alignment for a boolean, especially within a struct + } + + { // `distinct` types + // Originally, all type declarations would create a distinct type unless #type_alias was present. + // Now the behaviour has been reversed. All type declarations create a type alias unless `distinct` is present. + // If the type expression is `struct`, `union`, `enum`, `proc`, or `bit_field`, the types will always been distinct. + + Int32 :: i32; + #assert(Int32 == i32); + + My_Int32 :: distinct i32; + #assert(My_Int32 != i32); + + My_Struct :: struct{x: int}; + #assert(My_Struct != struct{x: int}); + } +} + + +union_type :: proc() { + fmt.println("\n# union_type"); + { + val: union{int, bool}; + val = 137; + if i, ok := val.(int); ok { + fmt.println(i); + } + val = true; + fmt.println(val); + + val = nil; + + switch v in val { + case int: fmt.println("int", v); + case bool: fmt.println("bool", v); + case: fmt.println("nil"); + } + } + { + // There is a duality between `any` and `union` + // An `any` has a pointer to the data and allows for any type (open) + // A `union` has as binary blob to store the data and allows only certain types (closed) + // The following code is with `any` but has the same syntax + val: any; + val = 137; + if i, ok := val.(int); ok { + fmt.println(i); + } + val = true; + fmt.println(val); + + val = nil; + + switch v in val { + case int: fmt.println("int", v); + case bool: fmt.println("bool", v); + case: fmt.println("nil"); + } + } + + Vector3 :: struct {x, y, z: f32}; + Quaternion :: struct {x, y, z, w: f32}; + + // More realistic examples + { + // NOTE(bill): For the above basic examples, you may not have any + // particular use for it. However, my main use for them is not for these + // simple cases. My main use is for hierarchical types. Many prefer + // subtyping, embedding the base data into the derived types. Below is + // an example of this for a basic game Entity. + + Entity :: struct { + id: u64, + name: string, + position: Vector3, + orientation: Quaternion, + + derived: any, + } + + Frog :: struct { + using entity: Entity, + jump_height: f32, + } + + Monster :: struct { + using entity: Entity, + is_robot: bool, + is_zombie: bool, + } + + // See `parametric_polymorphism` procedure for details + new_entity :: proc(T: type) -> ^Entity { + t := new(T); + t.derived = t^; + return t; + } + + entity := new_entity(Monster); + + switch e in entity.derived { + case Frog: + fmt.println("Ribbit"); + case Monster: + if e.is_robot do fmt.println("Robotic"); + if e.is_zombie do fmt.println("Grrrr!"); + } + } + + { + // NOTE(bill): A union can be used to achieve something similar. Instead + // of embedding the base data into the derived types, the derived data + // in embedded into the base type. Below is the same example of the + // basic game Entity but using an union. + + Entity :: struct { + id: u64, + name: string, + position: Vector3, + orientation: Quaternion, + + derived: union {Frog, Monster}, + } + + Frog :: struct { + using entity: ^Entity, + jump_height: f32, + } + + Monster :: struct { + using entity: ^Entity, + is_robot: bool, + is_zombie: bool, + } + + // See `parametric_polymorphism` procedure for details + new_entity :: proc(T: type) -> ^Entity { + t := new(Entity); + t.derived = T{entity = t}; + return t; + } + + entity := new_entity(Monster); + + switch e in entity.derived { + case Frog: + fmt.println("Ribbit"); + case Monster: + if e.is_robot do fmt.println("Robotic"); + if e.is_zombie do fmt.println("Grrrr!"); + } + + // NOTE(bill): As you can see, the usage code has not changed, only its + // memory layout. Both approaches have their own advantages but they can + // be used together to achieve different results. The subtyping approach + // can allow for a greater control of the memory layout and memory + // allocation, e.g. storing the derivatives together. However, this is + // also its disadvantage. You must either preallocate arrays for each + // derivative separation (which can be easily missed) or preallocate a + // bunch of "raw" memory; determining the maximum size of the derived + // types would require the aid of metaprogramming. Unions solve this + // particular problem as the data is stored with the base data. + // Therefore, it is possible to preallocate, e.g. [100]Entity. + + // It should be noted that the union approach can have the same memory + // layout as the any and with the same type restrictions by using a + // pointer type for the derivatives. + + /* + Entity :: struct { + ... + derived: union{^Frog, ^Monster}, + } + + Frog :: struct { + using entity: Entity, + ... + } + Monster :: struct { + using entity: Entity, + ... + + } + new_entity :: proc(T: type) -> ^Entity { + t := new(T); + t.derived = t; + return t; + } + */ + } +} + +parametric_polymorphism :: proc() { + fmt.println("# parametric_polymorphism"); + + print_value :: proc(value: $T) { + fmt.printf("print_value: %T %v\n", value, value); + } + + v1: int = 1; + v2: f32 = 2.1; + v3: f64 = 3.14; + v4: string = "message"; + + print_value(v1); + print_value(v2); + print_value(v3); + print_value(v4); + + fmt.println(); + + add :: proc(p, q: $T) -> T { + x: T = p + q; + return x; + } + + a := add(3, 4); + fmt.printf("a: %T = %v\n", a, a); + + b := add(3.2, 4.3); + fmt.printf("b: %T = %v\n", b, b); + + // This is how `new` is implemented + alloc_type :: proc(T: type) -> ^T { + t := cast(^T)alloc(size_of(T), align_of(T)); + t^ = T{}; // Use default initialization value + return t; + } + + copy_slice :: proc(dst, src: []$T) -> int { + n := min(len(dst), len(src)); + if n > 0 { + mem.copy(&dst[0], &src[0], n*size_of(T)); + } + return n; + } + + double_params :: proc(a: $A, b: $B) -> A { + return a + A(b); + } + + fmt.println(double_params(12, 1.345)); + + + + { // Polymorphic Types and Type Specialization + Table_Slot :: struct(Key, Value: type) { + occupied: bool, + hash: u32, + key: Key, + value: Value, + } + TABLE_SIZE_MIN :: 32; + Table :: struct(Key, Value: type) { + count: int, + allocator: Allocator, + slots: []Table_Slot(Key, Value), + } + + // Only allow types that are specializations of a (polymorphic) slice + make_slice :: proc(T: type/[]$E, len: int) -> T { + return make(T, len); + } + + + // Only allow types that are specializations of `Table` + allocate :: proc(table: ^$T/Table, capacity: int) { + c := context; + if table.allocator.procedure != nil do c.allocator = table.allocator; + + context <- c { + table.slots = make_slice(type_of(table.slots), max(capacity, TABLE_SIZE_MIN)); + } + } + + expand :: proc(table: ^$T/Table) { + c := context; + if table.allocator.procedure != nil do c.allocator = table.allocator; + + context <- c { + old_slots := table.slots; + + cap := max(2*len(table.slots), TABLE_SIZE_MIN); + allocate(table, cap); + + for s in old_slots do if s.occupied { + put(table, s.key, s.value); + } + + free(old_slots); + } + } + + // Polymorphic determination of a polymorphic struct + // put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) { + put :: proc(table: ^Table($Key, $Value), key: Key, value: Value) { + hash := get_hash(key); // Ad-hoc method which would fail in a different scope + index := find_index(table, key, hash); + if index < 0 { + if f64(table.count) >= 0.75*f64(len(table.slots)) { + expand(table); + } + assert(table.count <= len(table.slots)); + + hash := get_hash(key); + index = int(hash % u32(len(table.slots))); + + for table.slots[index].occupied { + if index += 1; index >= len(table.slots) { + index = 0; + } + } + + table.count += 1; + } + + slot := &table.slots[index]; + slot.occupied = true; + slot.hash = hash; + slot.key = key; + slot.value = value; + } + + + // find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) { + find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) { + hash := get_hash(key); + index := find_index(table, key, hash); + if index < 0 { + return Value{}, false; + } + return table.slots[index].value, true; + } + + find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int { + if len(table.slots) <= 0 do return -1; + + index := int(hash % u32(len(table.slots))); + for table.slots[index].occupied { + if table.slots[index].hash == hash { + if table.slots[index].key == key { + return index; + } + } + + if index += 1; index >= len(table.slots) { + index = 0; + } + } + + return -1; + } + + get_hash :: proc(s: string) -> u32 { // fnv32a + h: u32 = 0x811c9dc5; + for i in 0..len(s) { + h = (h ~ u32(s[i])) * 0x01000193; + } + return h; + } + + + table: Table(string, int); + + for i in 0..36 do put(&table, "Hellope", i); + for i in 0..42 do put(&table, "World!", i); + + found, _ := find(&table, "Hellope"); + fmt.printf("`found` is %v\n", found); + + found, _ = find(&table, "World!"); + fmt.printf("`found` is %v\n", found); + + // I would not personally design a hash table like this in production + // but this is a nice basic example + // A better approach would either use a `u64` or equivalent for the key + // and let the user specify the hashing function or make the user store + // the hashing procedure with the table + } +} + + + + +prefix_table := [?]string{ + "White", + "Red", + "Green", + "Blue", + "Octarine", + "Black", +}; + +threading_example :: proc() { + when ODIN_OS == "windows" { + fmt.println("# threading_example"); + + unordered_remove :: proc(array: ^[dynamic]$T, index: int, loc := #caller_location) { + __bounds_check_error_loc(loc, index, len(array)); + array[index] = array[len(array)-1]; + pop(array); + } + ordered_remove :: proc(array: ^[dynamic]$T, index: int, loc := #caller_location) { + __bounds_check_error_loc(loc, index, len(array)); + copy(array[index..], array[index+1..]); + pop(array); + } + + worker_proc :: proc(t: ^thread.Thread) -> int { + for iteration in 1...5 { + fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration); + fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration); + // win32.sleep(1); + } + return 0; + } + + threads := make([dynamic]^thread.Thread, 0, len(prefix_table)); + defer free(threads); + + for in prefix_table { + if t := thread.create(worker_proc); t != nil { + t.init_context = context; + t.use_init_context = true; + t.user_index = len(threads); + append(&threads, t); + thread.start(t); + } + } + + for len(threads) > 0 { + for i := 0; i < len(threads); /**/ { + if t := threads[i]; thread.is_done(t) { + fmt.printf("Thread %d is done\n", t.user_index); + thread.destroy(t); + + ordered_remove(&threads, i); + } else { + i += 1; + } + } + } + } +} + +array_programming :: proc() { + fmt.println("# array_programming"); + { + a := [3]f32{1, 2, 3}; + b := [3]f32{5, 6, 7}; + c := a * b; + d := a + b; + e := 1 + (c - d) / 2; + fmt.printf("%.1f\n", e); // [0.5, 3.0, 6.5] + } + + { + a := [3]f32{1, 2, 3}; + b := swizzle(a, 2, 1, 0); + assert(b == [3]f32{3, 2, 1}); + + c := swizzle(a, 0, 0); + assert(c == [2]f32{1, 1}); + assert(c == 1); + } + + { + Vector3 :: distinct [3]f32; + a := Vector3{1, 2, 3}; + b := Vector3{5, 6, 7}; + c := (a * b)/2 + 1; + d := c.x + c.y + c.z; + fmt.printf("%.1f\n", d); // 22.0 + + cross :: proc(a, b: Vector3) -> Vector3 { + i := swizzle(a, 1, 2, 0) * swizzle(b, 2, 0, 1); + j := swizzle(a, 2, 0, 1) * swizzle(b, 1, 2, 0); + return i - j; + } + + blah :: proc(a: Vector3) -> f32 { + return a.x + a.y + a.z; + } + + x := cross(a, b); + fmt.println(x); + fmt.println(blah(x)); + } +} + + +using println in import "core:fmt" + +using_in :: proc() { + fmt.println("# using in"); + using print in fmt; + + println("Hellope1"); + print("Hellope2\n"); + + Foo :: struct { + x, y: int, + b: bool, + } + f: Foo; + f.x, f.y = 123, 321; + println(f); + using x, y in f; + x, y = 456, 654; + println(f); +} + +named_proc_return_parameters :: proc() { + fmt.println("# named proc return parameters"); + + foo0 :: proc() -> int { + return 123; + } + foo1 :: proc() -> (a: int) { + a = 123; + return; + } + foo2 :: proc() -> (a, b: int) { + // Named return values act like variables within the scope + a = 321; + b = 567; + return b, a; + } + fmt.println("foo0 =", foo0()); // 123 + fmt.println("foo1 =", foo1()); // 123 + fmt.println("foo2 =", foo2()); // 567 321 +} + + +enum_export :: proc() { + fmt.println("# enum #export"); + + Foo :: enum #export {A, B, C}; + + f0 := A; + f1 := B; + f2 := C; + fmt.println(f0, f1, f2); +} + +explicit_procedure_overloading :: proc() { + fmt.println("# explicit procedure overloading"); + + add_ints :: proc(a, b: int) -> int { + x := a + b; + fmt.println("add_ints", x); + return x; + } + add_floats :: proc(a, b: f32) -> f32 { + x := a + b; + fmt.println("add_floats", x); + return x; + } + add_numbers :: proc(a: int, b: f32, c: u8) -> int { + x := int(a) + int(b) + int(c); + fmt.println("add_numbers", x); + return x; + } + + add :: proc[add_ints, add_floats, add_numbers]; + + add(int(1), int(2)); + add(f32(1), f32(2)); + add(int(1), f32(2), u8(3)); + + add(1, 2); // untyped ints coerce to int tighter than f32 + add(1.0, 2.0); // untyped floats coerce to f32 tighter than int + add(1, 2, 3); // three parameters + + // Ambiguous answers + // add(1.0, 2); + // add(1, 2.0); +} + +complete_switch :: proc() { + fmt.println("# complete_switch"); + { // enum + Foo :: enum #export { + A, + B, + C, + D, + } + + b := Foo.B; + f := Foo.A; + #complete switch f { + case A: fmt.println("A"); + case B: fmt.println("B"); + case C: fmt.println("C"); + case D: fmt.println("D"); + case: fmt.println("?"); + } + } + { // union + Foo :: union {int, bool}; + f: Foo = 123; + #complete switch in f { + case int: fmt.println("int"); + case bool: fmt.println("bool"); + case: + } + } +} + + +cstring_example :: proc() { + W :: "Hellope"; + X :: cstring(W); + Y :: string(X); + + w := W; + x: cstring = X; + y: string = Y; + z := string(x); + fmt.println(x, y, z); + fmt.println(len(x), len(y), len(z)); + fmt.println(len(W), len(X), len(Y)); + // IMPORTANT NOTE for cstring variables + // len(cstring) is O(N) + // cast(cstring)string is O(N) +} + +deprecated_attribute :: proc() { + @(deprecated="Use foo_v2 instead") + foo_v1 :: proc(x: int) { + fmt.println("foo_v1"); + } + foo_v2 :: proc(x: int) { + fmt.println("foo_v2"); + } + + // NOTE: Uncomment to see the warning messages + // foo_v1(1); +} + + +main :: proc() { + fmt.println("HERE\n"); + when true { + general_stuff(); + union_type(); + parametric_polymorphism(); + threading_example(); + array_programming(); + using_in(); + named_proc_return_parameters(); + enum_export(); + explicit_procedure_overloading(); + complete_switch(); + cstring_example(); + deprecated_attribute(); + } +} diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 2626610de..db666da46 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -531,11 +531,15 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) { check_decl_attributes(c, d->attributes, proc_decl_attribute, &ac); } - e->deprecated_message = ac.deprecated_message; ac.link_name = handle_link_name(c, e->token, ac.link_name, ac.link_prefix); - if (d->scope->package != nullptr && e->token.string == "main") { + AstPackage *package = nullptr; + if (d->scope->parent && d->scope->parent->is_package) { + package = d->scope->parent->package; + } + + if (package != nullptr && e->token.string == "main") { if (pt->param_count != 0 || pt->result_count != 0) { gbString str = type_to_string(proc_type); @@ -547,7 +551,7 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) { error(e->token, "Procedure 'main' cannot have a custom calling convention"); } pt->calling_convention = ProcCC_Contextless; - if (d->scope->is_init) { + if (package->kind == ImportedPackage_Init) { if (c->info.entry_point != nullptr) { error(e->token, "Redeclaration of the entry pointer procedure 'main'"); } else { diff --git a/src/checker.cpp b/src/checker.cpp index 2f4a37511..470cfe185 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -533,6 +533,7 @@ void init_universal_scope(void) { // NOTE(bill): No need to free these gbAllocator a = heap_allocator(); universal_scope = create_scope(nullptr, a); + universal_scope->is_package = true; // Types for (isize i = 0; i < gb_count_of(basic_types); i++) { @@ -2961,6 +2962,9 @@ void check_parsed_files(Checker *c) { for_array(j, p->files.entries) { AstFile *f = p->files.entries[j].value; create_scope_from_file(c, f); + HashKey key = hash_string(f->fullpath); + map_set(&c->info.files, key, f); + add_curr_ast_file(c, f); check_collect_entities(c, f->decls); } diff --git a/src/ir.cpp b/src/ir.cpp index ab5c38838..f20d2cafb 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -7735,7 +7735,8 @@ bool ir_gen_init(irGen *s, Checker *c) { String init_fullpath = c->parser->init_fullpath; if (build_context.out_filepath.len == 0) { - s->output_name = filename_from_path(init_fullpath); + // s->output_name = filename_from_path(init_fullpath); + s->output_name = str_lit("main"); s->output_base = s->output_name; } else { s->output_name = build_context.out_filepath; @@ -8255,9 +8256,17 @@ void ir_gen_tree(irGen *s) { for_array(i, info->entities) { Entity *e = info->entities[i]; String name = e->token.string; + + bool is_global = false; + if (e->scope->is_package) { + is_global = true; + } else if (e->scope->parent && e->scope->parent->is_package) { + is_global = true; + } + if (e->kind == Entity_Variable) { global_variable_max_count++; - } else if (e->kind == Entity_Procedure && !e->scope->is_global) { + } else if (e->kind == Entity_Procedure && !is_global) { if (e->scope->is_init && name == "main") { GB_ASSERT(e == entry_point); // entry_point = e; @@ -8306,9 +8315,16 @@ void ir_gen_tree(irGen *s) { GB_ASSERT(e->kind == Entity_Variable); + bool is_global = false; + if (e->scope->is_package) { + is_global = true; + } else if (e->scope->parent && e->scope->parent->is_package) { + is_global = true; + } + bool is_foreign = e->Variable.is_foreign; bool is_export = e->Variable.is_export; - bool no_name_mangle = e->scope->is_global || e->Variable.link_name.len > 0 || is_foreign || is_export; + bool no_name_mangle = is_global || e->Variable.link_name.len > 0 || is_foreign || is_export; String name = e->token.string; if (!no_name_mangle) { @@ -8353,6 +8369,9 @@ void ir_gen_tree(irGen *s) { continue; } + Scope *package_scope = scope->parent; + GB_ASSERT(package_scope->is_package); + switch (e->kind) { case Entity_Variable: // NOTE(bill): Handled above as it requires a specific load order @@ -8376,7 +8395,7 @@ void ir_gen_tree(irGen *s) { String original_name = name; - if (!scope->is_global || polymorphic_struct || is_type_polymorphic(e->type)) { + if (!package_scope->is_global || polymorphic_struct || is_type_polymorphic(e->type)) { if (e->kind == Entity_Procedure && e->Procedure.is_export) { } else if (e->kind == Entity_Procedure && e->Procedure.link_name.len > 0) { // Handle later diff --git a/src/main.cpp b/src/main.cpp index 9b08fda1f..733e45c30 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,11 +14,9 @@ #include "parser.cpp" #include "docs.cpp" #include "checker.cpp" -#if 0 #include "ir.cpp" #include "ir_opt.cpp" #include "ir_print.cpp" -#endif // NOTE(bill): 'name' is used in debugging and profiling modes i32 system_exec_command_line_app(char *name, bool is_silent, char *fmt, ...) { @@ -811,7 +809,7 @@ int main(int arg_count, char **arg_ptr) { check_parsed_files(&checker); -#if 0 +#if 1 if (build_context.no_output_files) { if (build_context.show_timings) { show_timings(&checker, &timings); diff --git a/src/parser.cpp b/src/parser.cpp index 7e3a79613..1a7ba68a3 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3831,7 +3831,6 @@ ParseFileError init_ast_file(AstFile *f, String fullpath, TokenPos *err_pos) { isize init_token_cap = cast(isize)gb_max(next_pow2(cast(i64)(file_size/2ll)), 16); array_init(&f->tokens, heap_allocator(), 0, gb_max(init_token_cap, 16)); - if (err == TokenizerInit_Empty) { Token token = {Token_EOF}; token.pos.file = fullpath; @@ -4166,6 +4165,9 @@ ParseFileError parse_imported_file(Parser *p, AstPackage *package, FileInfo *fi, AstFile *file = gb_alloc_item(heap_allocator(), AstFile); file->package = package; + p->file_index += 1; + file->id = p->file_index; + TokenPos err_pos = {0}; ParseFileError err = init_ast_file(file, fi->fullpath, &err_pos); diff --git a/src/parser.hpp b/src/parser.hpp index 686f3ed61..ed122baea 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -40,6 +40,7 @@ struct ImportedPackage { }; struct AstFile { + isize id; AstPackage * package; Scope * scope; @@ -104,6 +105,7 @@ struct Parser { isize total_line_count; gbMutex file_add_mutex; gbMutex file_decl_mutex; + isize file_index; }; enum ProcInlining { diff --git a/src/string.cpp b/src/string.cpp index b63de5c76..32d97184a 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -259,7 +259,10 @@ bool string_contains_char(String const &s, u8 c) { String filename_from_path(String s) { isize i = string_extension_position(s); - s = substring(s, 0, i); + if (i >= 0) { + s = substring(s, 0, i); + return s; + } if (i > 0) { isize j = 0; for (j = s.len-1; j >= 0; j--) {