updating some examples and bugfixes
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 12 Apr 2021 16:00:38 +0000 (11:00 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 12 Apr 2021 16:00:38 +0000 (11:00 -0500)
13 files changed:
bin/onyx
core/alloc/pool.onyx
core/builtin.onyx
docs/bugs
docs/compile_time_vars
docs/plan
examples/12_varargs.onyx
examples/13_use_keyword.onyx
progs/odin_example.onyx
src/onyx.c
src/onyxutils.c
tests/array_struct_robustness
tests/array_struct_robustness.onyx

index 5e9795c52834ec7a4bba1215c8614db40fc2a096..ad5f0d5c5860ba6254d510f9107f5e57239ebebc 100755 (executable)
Binary files a/bin/onyx and b/bin/onyx differ
index 0f6ff50bbae40d4975c32a94b3b277f5b3dbf843..2a52d12180b6388e8d86c245daa4088b7bb9c990 100644 (file)
@@ -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
+}
index 7a7fd34c45144057966c99ba54727353281c6bae..aa742eb842f590813f2a67e7b847632fee044f23 100644 (file)
@@ -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;
     }
 }
 
index 56a444387126ced56a697ab23986a65298068333..a174a863425e66563b7e901197493e004eb6b16c 100644 (file)
--- 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.
index b784f619ea97cdf10e1f8953e162f24409882087..42ecb1bbb6034de433557a6f6d83f55c5fb9821c 100644 (file)
@@ -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
index 1cd882884ecb6cda1c150b0137eeed90fe16d536..081487fd32806411cbfac92f35e769f82240c192 100644 (file)
--- 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
 
index ddf56f41c5dd009bcefd9aa428a6f83486b5c8e7..268d1013352a6e9161968ded057b6a10a2b85b90 100644 (file)
@@ -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.
 }
index ddf56f41c5dd009bcefd9aa428a6f83486b5c8e7..760a81a25837c05bbe83c1f58ac74e97a87fbe1b 100644 (file)
@@ -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.    
+    }
 }
index 566a3d20d0ed67aecbac6cd4f8db01eb7bde484a..9a3074136e90d26285986d8b7d6435cdb45efbe4 100644 (file)
@@ -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;
index 5030c3f1fdeabadf459ba98c4469274e1fdbe467..f9db59a261c3414b1a1995bf1f4666475a685eaa 100644 (file)
@@ -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
index b7d219fd6a459eb3068402b2530f3b604968e99a..67af166c4f383c18539bac5e57d078f8567f2b9d 100644 (file)
@@ -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;
index 038649dffaa54d0fa619b12363f60354dd853acd..434a660476c2b58a52c3bb8818a17d4a0186e287 100644 (file)
@@ -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)
index 806b8656f39d410343fdd09d067f02b0f89eff7c..dc169bc8397171167d690b581f4be5a193e09c20 100644 (file)
@@ -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]);
+    }
 }