IR now builds with the new package system

This commit is contained in:
gingerBill
2018-05-27 10:49:14 +01:00
parent eb11edabe0
commit 7ee9051a56
9 changed files with 800 additions and 13 deletions

View File

@@ -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

755
examples/demo/demo.odin Normal file
View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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--) {