From: Brendan Hansen Date: Mon, 12 Apr 2021 16:00:38 +0000 (-0500) Subject: updating some examples and bugfixes X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=ca106878665053b3a342a4b50f35b83b06252fb8;p=onyx.git updating some examples and bugfixes --- diff --git a/bin/onyx b/bin/onyx index 5e9795c5..ad5f0d5c 100755 Binary files a/bin/onyx and b/bin/onyx differ diff --git a/core/alloc/pool.onyx b/core/alloc/pool.onyx index 0f6ff50b..2a52d121 100644 --- a/core/alloc/pool.onyx +++ b/core/alloc/pool.onyx @@ -54,6 +54,10 @@ pool_free :: (pool: ^PoolAllocator($Elem), elem: ^Elem) { // This could become: proc (buffer: [] u8, $Elem: type_expr) -> PoolAllocator(Elem) // when that feature is implemented. +// +// I think I'm going to veto that idea because when the buffer is a slice of Elem +// its guaranteed that the size allocated for the buffer is a multiple of the size +// of Elem. make :: (buffer: [] $Elem) -> PoolAllocator(Elem) { assert(sizeof Elem >= sizeof rawptr, "Cannot have a pool allocator of a type less than a rawptr in size."); @@ -74,4 +78,4 @@ make_allocator :: (pool: ^PoolAllocator($Elem)) -> Allocator { func = #solidify pool_allocator_proc { Elem = Elem }, data = pool, }; -} \ No newline at end of file +} diff --git a/core/builtin.onyx b/core/builtin.onyx index 7a7fd34c..aa742eb8 100644 --- a/core/builtin.onyx +++ b/core/builtin.onyx @@ -30,12 +30,12 @@ vararg_get :: proc { return true; }, - (va: vararg, $T: type_expr, default: T = 0) -> T { - if va.count <= 0 do return default; + (va: vararg, $T: type_expr, default: T = 0) -> (bool, T) { + if va.count <= 0 do return false, default; ret := *cast(^T) va.data; va.data = cast(rawptr) (cast(^u8) va.data + sizeof T); va.count -= 1; - return ret; + return true, ret; } } diff --git a/docs/bugs b/docs/bugs index 56a44438..a174a863 100644 --- a/docs/bugs +++ b/docs/bugs @@ -37,6 +37,8 @@ List of known bugs: } [ ] There is a segfault when passing an anonymous struct literal to an untyped vararg. + Should just need to detect this case and provide an error message since this doesn't + make any sense semantically. vararg_proc :: (va: ...) { ... @@ -53,6 +55,15 @@ List of known bugs: The problem is that 'T' is an unresolved symbol because the context of the function type does not have the solved value of T in it. +[ ] Top level implicit typed arrays/struct literals are not correctly being marked as compile-time. + + Vec2 :: struct { x, y: i32; } + + Hex_Directions := Vec2.[ + .{ 1, 0 }, .{ 1, -1 }, .{ 0, -1 }, + .{ -1, 0 }, .{ -1, 1 }, .{ 0, 1 }, + ]; + List of things to change: [X] Currently, there is no way to use the initialized members of a structure without using a struct literal. diff --git a/docs/compile_time_vars b/docs/compile_time_vars index b784f619..42ecb1bb 100644 --- a/docs/compile_time_vars +++ b/docs/compile_time_vars @@ -6,7 +6,7 @@ that give information about the current compilation. These would include things The standard library will be rewritten to use these variables to conditionally include file that are only supported on a particular system. -For the moment, I think that the choice of backend will be given by compiler flags. It will +For the moment, I think that the choice of runtime will be given by compiler flags. It will set the variables described below to their value. There should be an #error directive that when hit just produces a compile time error and prevents diff --git a/docs/plan b/docs/plan index 1cd88288..081487fd 100644 --- a/docs/plan +++ b/docs/plan @@ -69,7 +69,7 @@ HOW: - This means identifiers can be compared using ptr comparison, instead of string comparison - This mean no more token_toggle_end!! Woo!! - [ ] 'when' statements + [X] 'when' statements - Compile time conditions - Only evalutate code blocks that evaluate to be true diff --git a/examples/12_varargs.onyx b/examples/12_varargs.onyx index ddf56f41..268d1013 100644 --- a/examples/12_varargs.onyx +++ b/examples/12_varargs.onyx @@ -1,6 +1,79 @@ +// Variadic procedures are a very useful feature in a lot of programming languages +// today so you have probably come across them at some point. If you haven't, +// "variadic" just means that the number of arguments the procedure takes varies. +// This is a slightly different concept from overloaded procedures, where there are +// multiple versions of a procedure and which one is chosen depends on the arguments +// given. Variadic procedures can consume as many arguments as they want. + +// In Onyx, there are two kinds of variadic procedures: fixed type, and untyped. In +// fixed type variadic procedures, all variadic procedures need to be of a specified type. +// In untyped variadic procedures, each of the variadic arguments can be of any type, +// similar to how they work in C. + +// Some little things to note about the current implementation of variadic arguments: +// - There can be at most 1 variadic argument +// - 'variadic argument' is shortened to 'vararg' in many situtations + + #load "core/std" use package core main :: (args: [] cstr) { + + // This is how you specify a fixed type variadic procedure. 'args' can be thought + // of as a slice of integers, with data and count fields. You can iterate over the + // varargs using a for-loop like any other array-like structure. Note, do not + // take a pointer to anything in the varargs because they are placed on the stack + // and will "disappear" when the procedure returns. + typed_varargs :: (args: ..i32) { + print("Typed variadic arguments: "); + for x: args { + printf("%i ", x); + } + print("\n"); + } + + typed_varargs(1, 2, 3, 4); + + // This is how you specify an untyped variadic procedure. Notice the extra '.' + // in the type. The access the arguments, you need to use the builtin procedure, + // 'vararg_get'. This procedure has two overloads that you can use: + // + // vararg_get :: (va: vararg, res: ^$T) -> bool + // vararg_get :: (va: vararg, $T: type_expr, default: T) -> (bool, T) + // + // The 'bool' returned is whether or not retrieving the vararg was successful. This + // can be achieved because in Onyx, untyped varargs know how many varargs are passed + // through a runtime count. This means if you call vararg_get more times than there + // are varargs, you will get empty or default results back. + untyped_varargs :: (args: ...) { + print("Untyped variadic arguments: "); + + x: i32; + while vararg_get(args, ^x) { + printf("%p ", x); + } + + print("\n"); + } + + untyped_varargs(1.0f, 2.0f, 3.0f, 4.0f); + + + + + // Most of the time with untyped varargs, you will have some kind of format + // specifier to indicate how many and of what type the varargs are. For example + // 'printf' uses this: + // + // printf :: (format: str, va: ...) -> void + // printf("%i %f %l %d", int, float, long, double); + // + // There are plans for the future to have better runtime type information available, + // which would allow you to introspect the types of each vararg at runtime. This + // would mean that instead of using '%i' style specifiers in printf, you could use + // '{}' to indicate the next variadic argument, whose type is automatically determined. + // If I'm not explaining this very well I apologize and hope that when the feature + // is implemented, it will make sense why it is a good idea. } diff --git a/examples/13_use_keyword.onyx b/examples/13_use_keyword.onyx index ddf56f41..760a81a2 100644 --- a/examples/13_use_keyword.onyx +++ b/examples/13_use_keyword.onyx @@ -1,6 +1,99 @@ +// Controlling the symbols in scope is important to maintaining well structured +// and factored code. In Onyx, the most powerful feature available to do this is +// the 'use' keyword. It allows you to bring in symbols from another namespace into +// the currently scope. In Onyx, a "namespace" simply referers to something contains +// names, generally something you can use the "." operator on. Some of the examples +// include: +// - structs with static (::) members +// - struct members +// - variables with a struct or pointer to a struct type +// - packages +// - enums + +// Please note, 'use' is a rather "alpha-tier" feature at the moment, meaning it works in +// most cases, but can easily break and require you to write the code without it. This +// is being address and hopefully in the next couple months it will be much more robust. + #load "core/std" use package core main :: (args: [] cstr) { + // Here are the uses of 'use' currently. + + { + // You can 'use' a package. You hopefully already knew this. + // This brings in all the symbols from the core.alloc package into + // this scope. + use package core.alloc + + // You can also restrict and partially rename the symbols that collide + use package core.string { free as string_free } + } + + { + // You can also 'use' a package that is a subpackage of another package. Here, 'string' + // is a subpackage of 'core' and the above 'use package core' makes this package + // available for you to write code like 'string.equal(...)'. However, because it is + // a namespace, you can 'use' it. + use string + + s := " Some string ..."; + t := strip_leading_whitespace(s); // This would have to be 'string.strip_leading_whitespace' otherwise. + println(t); + } + + { + // You can 'use' parameters to a function. This effectively makes this + // procedure behave like a 'method' for Dummy. You can access all members + // of dummy with having to say 'dummy.' everywhere. Note, this is one of the + // most alpha-tier parts of 'use', so if you are running into error, report + // an issue and (sadly) go back to writing without the 'use'. + // + // One side note, you can 'use' more than one parameter, and it doesn't have to + // be the first parameter. This can create decent self-documenting code because + // a procedure with two used parameters implies that it is a 'cross-cutting' + // procedure between two areas of concern. + + basically_a_method :: (use dummy: ^Dummy) { + printf("Dummy with name '%s', and age '%i'.\n", name, age); + } + + dummy := Dummy.{ "Phil", 15 }; + basically_a_method(^dummy); + + Dummy :: struct { + name : str; + age : i32; + } + } + + { + // You can 'use' an enum. This is great if you have a burried enum in some other namespace + // and want to do a switch over that enum. + + Player :: struct { + Color :: enum { + Undefined; + Red; Green; Blue; + } + + color := Color.Red; + } + + value := Player.{}; + + use Player.Color; + switch value.color { + case Red do println("The color is red!"); + case Green do println("The color is green!"); + case Blue do println("The color is blue!"); + case #default do println("The color is unknown..."); + } + } + + { + // You can 'use' struct members that are structures. + // This is already explained in the struct example. + } } diff --git a/progs/odin_example.onyx b/progs/odin_example.onyx index 566a3d20..9a307413 100644 --- a/progs/odin_example.onyx +++ b/progs/odin_example.onyx @@ -69,7 +69,7 @@ f :: () -> [5] [2] u32 #export "IAMTHEFUNCTION" { return mem; } -compress :: (arr: [$N] $T, f : proc (T, T) -> T) -> T { +compress :: (arr: [$N] $T, f: (T, T) -> T) -> T { val := arr[0]; for i: 1..N do val = f(val, arr[i]); return val; diff --git a/src/onyx.c b/src/onyx.c index 5030c3f1..f9db59a2 100644 --- a/src/onyx.c +++ b/src/onyx.c @@ -370,33 +370,23 @@ static void output_dummy_progress_bar() { if (bh_arr_length(eh->entities) == 0) return; static const char* state_colors[] = { - "\e[91m", - "\e[93m", - "\e[97m", - "\e[93m", - "\e[94m", - "\e[95m", - "\e[94m", - "\e[95m", - "\e[96m", - "\e[92m", + "\e[91m", "\e[93m", "\e[97m", "\e[93m", "\e[94m", + "\e[95m", "\e[94m", "\e[95m", "\e[96m", "\e[92m", }; printf("\e[2;1H"); for (i32 i = 0; i < Entity_State_Count - 1; i++) { - if (i % 4 == 0) printf("\n"); - printf("%s \xe2\x96\x88 %s", state_colors[i], entity_state_strings[i]); + if (i % 2 == 0) printf("\n"); + printf("%s %25s \xe2\x96\x88 ", state_colors[i], entity_state_strings[i]); } printf("\n\n"); for (i32 i = 0; i < Entity_Type_Count; i++) { - if (eh->type_count[i] == 0) { - printf("\e[90m"); - } else { - printf("\e[97m"); - } + if (eh->type_count[i] == 0) printf("\e[90m"); + else if ((i32) eh->entities[0]->type == i) printf("\e[92m"); + else printf("\e[97m"); printf("%25s (%4d) | ", entity_type_strings[i], eh->type_count[i]); @@ -407,9 +397,7 @@ static void output_dummy_progress_bar() { printf(state_colors[j]); i32 count = (eh->all_count[j][i] >> 5) + 1; - for (i32 c = 0; c < count * 2; c++) { - printf("\xe2\x96\x88"); - } + for (i32 c = 0; c < count * 2; c++) printf("\xe2\x96\x88"); printf("\e[0m"); } @@ -426,7 +414,6 @@ static i32 onyx_compile() { while (!bh_arr_is_empty(context.entities.entities)) { Entity* ent = entity_heap_top(&context.entities); - entity_heap_remove_top(&context.entities); #if defined(_BH_LINUX) if (context.options->fun_output) { @@ -441,7 +428,8 @@ static i32 onyx_compile() { usleep(1000); } #endif - + + entity_heap_remove_top(&context.entities); b32 changed = process_entity(ent); // NOTE: VERY VERY dumb cycle breaking. Basically, remember the first entity that did diff --git a/src/onyxutils.c b/src/onyxutils.c index b7d219fd..67af166c 100644 --- a/src/onyxutils.c +++ b/src/onyxutils.c @@ -862,7 +862,7 @@ AstNode* polymorphic_proc_try_solidify(AstPolyProc* pp, bh_arr(AstPolySolution) AstPolyProc* new_pp = onyx_ast_node_new(context.ast_alloc, sizeof(AstPolyProc), Ast_Kind_Polymorphic_Proc); new_pp->token = pp->token; // TODO: Change this to be the solidify->token new_pp->base_func = pp->base_func; - new_pp->poly_scope = new_pp->poly_scope; + new_pp->poly_scope = new_pp->poly_scope; // CLEANUP: This doesn't seem correct?? new_pp->flags = pp->flags; new_pp->poly_params = pp->poly_params; @@ -1033,6 +1033,15 @@ void report_unable_to_match_overload(AstCall* call) { // // Polymorphic Structures // +// +// Currently, I am not very happy about how polymorphic structure generation works. My biggest problem +// with it is that it is very different from the polymorhic procedure generation. Also, it needs to +// completely generate and check the structure right away, which means there is a lot of up-front work +// done here that could probably be done elsewhere. This really relates to a large problem in the compiler +// that types need to be known completely by the time symbol resolution is done, even though that +// information shouldn't need to be known until right before the types are checked. +// + char* build_poly_struct_name(AstPolyStructType* ps_type, Type* cs_type) { char name_buf[256]; fori (i, 0, 256) name_buf[i] = 0; diff --git a/tests/array_struct_robustness b/tests/array_struct_robustness index 038649df..434a6604 100644 --- a/tests/array_struct_robustness +++ b/tests/array_struct_robustness @@ -13,3 +13,12 @@ Vec2(25, 125) Vec2(100, 1000) Array of u32. 63001 +2D-array from an allocation to a function. +Vec2(1000, 2000) +Vec2(1001, 2001) +Vec2(1002, 2004) +Vec2(1003, 2009) +Vec2(1004, 2016) +Vec2(1005, 2025) +Vec2(1006, 2036) +Vec2(1007, 2049) diff --git a/tests/array_struct_robustness.onyx b/tests/array_struct_robustness.onyx index 806b8656..dc169bc8 100644 --- a/tests/array_struct_robustness.onyx +++ b/tests/array_struct_robustness.onyx @@ -95,4 +95,30 @@ main :: (args: [] cstr) { println(nums[50]); } + + { + println("2D-array from an allocation to a function."); + + vecs_data: [2][4] Vec2; + vecs := ^vecs_data; // equivalent of heap allocating it + + set_vecs :: (vecs: ^[2][4] Vec2) { + i := 0; + for ^row: *vecs { + for ^v: *row { + *v = .{ 1000 + i, 2000 + i * i }; + i += 1; + } + } + } + + set_vecs(vecs); + + print_vecs :: (vecs: ^[4] Vec2) { + for ^v: *vecs do println(*v); + } + + print_vecs(^(*vecs)[0]); + print_vecs(^(*vecs)[1]); + } }