small bugfixes; started writing example programs with docs
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 5 Sep 2020 20:53:20 +0000 (15:53 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 5 Sep 2020 20:53:20 +0000 (15:53 -0500)
.vimspector.json
core/array.onyx
core/stdio.onyx
docs/plan
examples/01_hello_world.onyx [new file with mode: 0644]
examples/02_variables.onyx [new file with mode: 0644]
examples/03_basics.onyx [new file with mode: 0644]
misc/onyx.vim
onyx
progs/poly_test.onyx
src/onyxwasm.c

index 899b511c32ce13ca2eeb9c41aa3d9b9a230d0e77..2e8fdc8bc6a82a682478cc53793fc7e01e9bbb7e 100644 (file)
@@ -6,7 +6,7 @@
                 "type": "cppdbg",
                 "request": "launch",
                 "program": "${workspaceFolder}/onyx",
-                "args": ["-verbose", "progs/poly_test.onyx"],
+                "args": ["-verbose", "examples/02_variables.onyx"],
                 "stopAtEntry": true,
                 "cwd": "${workspaceFolder}",
                 "environment": [],
index 0d314deb33183d1dc820b31214db92b9d0d58296..d6a580693e4c3d9402caf224205007e2006a5b46 100644 (file)
@@ -75,7 +75,7 @@ array_fast_delete :: proc (arr: ^[..] $T, idx: u32) {
 }
 
 array_contains :: proc (arr: ^[..] $T, x: T) -> bool {
-    for i: 0 .. arr.count do if arr.data[i] == x do return true;
+    for it: *arr do if it == x do return true;
     return false;
 }
 
@@ -120,5 +120,5 @@ array_fold :: proc (arr: ^[..] $T, init: $R, f: proc (T, R) -> R) -> R {
 }
 
 array_map :: proc (arr: ^[..] $T, f: proc (T) -> T) {
-    for i: 0 .. arr.count do arr.data[i] = f(arr.data[i]);
+    for ^it: *arr do *it = f(*it);
 }
index f4c4cd6f590954b74a6050ae9f696fbed2478f37..e2cfac11a033a79ff23dc7055219b6167119593c 100644 (file)
@@ -22,10 +22,19 @@ print_i32     :: proc (n: i32, base := 10)  do string_builder_append(^print_buff
 print_bool    :: proc (b: bool) do string_builder_append(^print_buffer, b);
 print_ptr     :: proc (p: rawptr) do string_builder_append(^print_buffer, cast(i64) p, 16l);
 
+print_range :: proc (r: range, sep := " ") {
+    for i: r {
+        print(i);
+        if i + r.step < r.high do print(sep);
+    }
+    print("\n");
+}
+
 print :: proc #overloaded {
        print_string, print_cstring,
        print_i64,    print_i32,
        print_bool,   print_ptr,
+    print_range,
 }
 
 // This works on both slices and arrays
@@ -44,7 +53,7 @@ print_buffer_flush :: proc {
        ^print_buffer
                |> string_builder_to_string()
                |> system.output_string();
-               
+
        ^print_buffer |> string_builder_clear();
 }
 
index 8b0c0b6272edb2f6afc8dad99196f2eb7c990b9f..d11718c28dd0ff41991a8937368312683c83af5f 100644 (file)
--- a/docs/plan
+++ b/docs/plan
@@ -241,6 +241,10 @@ HOW:
                 * slice
                 * dynamic array
 
+        [ ] Don't include foreign functions unless they're used
+            - Do multiple passes if needed
+            - Some APIs like WebGL have a ton of foreigns, and most of them aren't even used
+
         [ ] baked parameters
             - Compile time known parameters
             - Removes the argument from the list and replaces the function with the
diff --git a/examples/01_hello_world.onyx b/examples/01_hello_world.onyx
new file mode 100644 (file)
index 0000000..4e1e7e6
--- /dev/null
@@ -0,0 +1,60 @@
+package main
+// Every file is part of a package, which is specified on the first line of the file.
+// If no package name is specified, the file is implicitly part of the 'main' package.
+// So, the package specification above is redudant, but is there for sake of completeness.
+
+// There are many source files that make up the 'core' package. While you can include them
+// all separately, they can all be included by including 'core/std/<target>'.
+//
+// Right now, there are two targets, wasi and js. They are mostly the same, except the js
+// target does not include functionality for file systems, since js does not have direct
+// access to the file system like wasi does.
+//
+// It is important to note, that these 'includes' are not C-style includes. They are NOT
+// textual substitutions. They simply tell the compiler that another file should be
+// added to the queue of files to load. When all files in the queue have been parsed,
+// the compiler can continue with the compilation process. You can also include the
+// same file as many times as you want. The redudant copies will be discarded.
+#include_file "core/std/wasi"
+
+// All of the functionality we need is in the 'core' package. Unlike other package systems,
+// there is no way to reference symbols in a package without specifically 'using' it. This
+// prevents a cluttered global namespace.
+//
+// In Onyx, there are a couple way to control the 'use package' statement. The example below
+// brings in ALL symbols from the 'core' package to the current package scope. This is handy,
+// but if there are symbols with the same name in multiple packages, there will be conflicts.
+// To address this, Onyx has two ways of controlling which symbols are included.
+//
+//      use package core as core_pkg
+//
+// The above makes all symbols in the 'core' package available under the 'core_pkg' symbol,
+// i.e. core_pkg.some_symbol. This is called a qualified use. The name does not have to be
+// different from the package name, so you could say something like, 'use package core as core'.
+//
+//      use package core { some_symbol, some_other_symbol as sos }
+//
+// This next exmaple makes the symbol, 'some_symbol', accessible in the current package scope.
+// This is useful if there is only one thing you need from a package and you do not want to
+// worry about conflicting symbol names in the future. This example also makes the symbol,
+// 'some_other_symbol', accessible in the current package scope under the name, 'sos'. This
+// is useful if a symbol name is long and you use it a lot in a package.
+//
+// As you may expect, you can also combine both of these features into one statement, such as,
+//
+//      use package core as core_pkg { some_symbol, some_other_symbol as sos }
+use package core
+
+// This is the main procedure. It is a convention when using the 'core/std/...' files that this
+// procedure is called when the program starts. There is nothing really special going on here;
+// you can go look in core/sys/wasi.onyx to see how the actual _start procedure works. At the
+// end it simply calls main.main, which is this procedure. By convention, main takes a slice
+// of cstrings, which were the arguments passed from the command line. We will talk about
+// slices later.
+main :: proc (args: [] cstring) {
+
+    // This is the actual call to print, which will print the message 'Hello World!\n' onto the screen.
+    // It is not too important to know how this works, but if you are interested you can find the
+    // definition for print at core/stdio.onyx.
+    print("Hello World!\n");
+}
diff --git a/examples/02_variables.onyx b/examples/02_variables.onyx
new file mode 100644 (file)
index 0000000..253c7fb
--- /dev/null
@@ -0,0 +1,30 @@
+// This time, we are not adding, 'package main' to the top of the file, since
+// every file is automatically part of the main package unless specified otherwise.
+
+#include_file "core/std/wasi"
+
+use package core
+
+main :: proc (args: [] cstring) {
+
+    // This is the syntax for declaring a local variable in a procedure.
+    foo: i32;
+    foo = 10;
+
+    // You don't have to put the declaration and assignment on different lines.
+    footwo: i32 = 10;
+
+    // If you leave the type out between the : and the =, the type is automatically
+    // inferred from the right hand side of the equals.
+    bar := 15;
+
+    print("foo is ");
+    print(foo);
+    print("\n");
+    print("footwo is ");
+    print(footwo);
+    print("\n");
+    print("bar is ");
+    print(bar);
+    print("\n");
+}
diff --git a/examples/03_basics.onyx b/examples/03_basics.onyx
new file mode 100644 (file)
index 0000000..0ce0ecb
--- /dev/null
@@ -0,0 +1,178 @@
+// Now, lets go over the basic types and control flow mechanisms in Onyx.
+
+#include_file "core/std/wasi"
+
+use package core
+
+main :: proc (args: [] cstring) {
+    // Here is a list of all the builtin types:
+    //       void
+    //       bool
+    //         i8  u8     (signed vs unsigned variants)
+    //        i16  u16
+    //        i32  u32
+    //        i64  u64
+    //        f32  f64
+    //     rawptr
+    //     string
+    //    cstring
+    //      range
+    //
+    // If you look in core/builtin.onyx, you can see the definition for some
+    // of these types in terms of other types. Let's look at some of those types.
+
+    // A pointer in Onyx is written as ^<type>. This is read as 'a pointer to a <type>'.
+    // I will not type to explain what a pointer is here, but it is something to note
+    // that pointers in Onyx are only 4-bytes instead of 8-bytes, since Onyx's compile
+    // target is WASM32.
+    //
+    // Here foo is an i32. The unary prefix ^ operator takes the address of a value.
+    // The type of foo_ptr is ^i32, or a pointer to an i32. Another example of a pointer
+    // is the cstring builtin. It is ^u8, which is what C would call a char*.
+    foo     := 10;
+    foo_ptr := ^foo;
+
+    print("foo is ");
+    print(foo);
+    print("\n");
+    print("foo_ptr is ");
+    print(foo_ptr);
+    print("\n");
+
+
+    // An important type to know before proceeding is the range type. When designing
+    // the language, there were a couple places where the idea of a range as very
+    // natural, such as for loop iteration, and making slices. The range type in
+    // Onyx is simply defined as a structure with 3 members: low, high, and step.
+    // A range represents values between low and high, including low but not including
+    // high, with a certain step between them. For example a range with values 5, 25, 2,
+    // represents the numbers 5, 7, 9, .. 21, 23. While you can use the range structure,
+    // it is more common to use a range literal, which is written below. 'rng' represents
+    // a range with a low of 5, a high of 25, and a step of 1. Currently there is no way
+    // to specify the step in a range literal.
+    rng := 5 .. 25;
+    print("rng is ");
+    print(rng);
+
+    // Onyx has 3 different array-like types:
+    //  * Fixed-size arrays, written [N] <type>, where N is a compile time know integer.
+    //  * Slices, written [] <type>.
+    //  * Dynamic arrays, written [..] <type>.
+    //
+    // Fixed size arrays are not commonly used because of their limited flexibility.
+    // They are commonly seen as struct members or local variables. Here is an example
+    // of creating and using a fixed size array. This uses a for-loop which we have not
+    // looked at yet, but the example should be self-explanitory.
+    fixed_arr : [10] i32;
+    for i: 0 .. 10 {
+        fixed_arr[i] = i * i;
+    }
+    print("fixed_arr[3] is ");
+    print(fixed_arr[3]);
+    print("\n");
+
+
+    // Slices are a concept not unique to Onyx. They represent a pointer and a count,
+    // which allows them to be passed around conveying multiple pieces of information.
+    // To create a slice, you either need a pointer or a fixed size array. Simply, do
+    // an array access with the type of the index being a range. This creates a slice.
+    //
+    // Think of this example as letting slice_arr be a sub-array of fixed_arr. When
+    // the 3rd element of slice_arr is accessed, it corresponds to the 6th element
+    // of fixed_arr. It is important to know that this does NOT make a copy of the
+    // data. It simply points into the same memory as fixed_arr.
+    slice_arr := fixed_arr[3 .. 9];
+    print("slice_arr[3] is ");
+    print(slice_arr[3]);
+    print("\n");
+
+
+    // Dynamic arrays are the most common arrays used in practice. They represent a
+    // pointer, a count, and a capacity. The extra capacity field allows implementations
+    // to know how many elements can fit into the space allocated for the array, and not
+    // just how many elements are currently in the array. To facilitate using dynamic
+    // arrays, there are many array_ procedures in the core package. Take this example.
+    dyn_arr : [..] i32;
+    array_init(^dyn_arr);
+    defer array_free(^dyn_arr);
+
+    for i: 0 .. 100 {
+        array_push(^dyn_arr, i * i * i);
+    }
+
+    print("dyn_arr is ");
+    print_array(dyn_arr);
+
+
+
+    // I think that's enough about types for now. Lets look at the ways to manage control
+    // flow in an Onyx program.
+
+    // An important things to know before looking at control flow are the different ways
+    // Onyx allows you to create a block. Blocks are used by all methods of control flow
+    // and can be declared in one of three ways:
+    //      * { ... } - Curly braces surrounding a set of semi-colon delimited statements.
+    //      * do ...  - The do keyword followed by one statement.
+    //      * ---     - The empty block which has no statements, equivalent to {}.
+
+    // Onyx has standard if statement semantics. Each if statement contains a condition,
+    // followed by a block. For this reason if-elseif chains have to be written with an
+    // elseif keyword. Otherwise, you would have to write 'else do if', which I find to
+    // be very ugly to read and is technically different, more on that later.
+
+    if_var := 5 * 2 + 3;
+    if if_var == 13 {
+        print("if_var was 13\n");
+        print("another statement here!\n");
+    }
+    elseif if_var == 14 do print("if_var was actually 14\n");
+    else do print("if_var wasn't 13 or 14\n");
+
+    // Ifs, as well as whiles and switch statements, can have a single initialized variable
+    // that is scoped to the blocks of the if statement and all else statements.
+
+    if hidden_if_var := 6; hidden_if_var > 5 {
+        print("hidden_if_var was greater than 5!\n");
+    }
+
+    // If you uncomment this line, you will get an error, since hidden_if_var is only accessible
+    // inside the if's block.
+    // print(hidden_if_var);
+
+
+    // Onyx has the standard top-test while loop, whose syntax is very similar to an if
+    // statement. The only unique thing about Onyx's while loops, is that they can have
+    // a 'else' clause. If the body of the loop is never run, i.e. the condition was
+    // false originally, then the else clause is used. Try changing the initial value
+    // for i to be 11.
+
+    while i := 0; i < 10 {
+        print(i);
+        print(" ");
+        i += 1;
+    } else {
+        print("The while loop never ran...");
+    }
+
+    print("\n");
+
+
+    // Onyx also has the standard 'break' and 'continue' statement found in many other
+    // imperative programming languages, but with a slight twist. It is not uncommon to
+    // be in a nested loop and want to either completely break out of all of the loops,
+    // or to skip to the next iteration of an outer loop. For this reason, Onyx allows
+    // you to place as many 'break' or 'continue' statements in a row as you need to
+    // break or continue the loop that you want to. Take this example.
+    while i := 0; i < 10 {
+        while j := 0; j < 10 {
+            print(i);
+            print(" ");
+            print(j);
+            print("\n");
+
+            if i == 1 && j == 2 do break break;
+            j += 1;
+        }
+        i += 1;
+    }
+}
index eceb008a7c9da5041f76e37a8bf4fb36ad9f527a..1f1c588eba7376a96609578cd2815d83449c806c 100644 (file)
@@ -10,7 +10,7 @@ endif
 let s:cpo_save = &cpo
 set cpo&vim
 
-syn keyword onyxKeyword package struct proc use global
+syn keyword onyxKeyword package struct enum proc use global
 syn keyword onyxKeyword if elseif else
 syn keyword onyxKeyword for while do
 syn keyword onyxKeyword switch case
@@ -32,7 +32,7 @@ syn keyword onyxCommentStart    contained TODO NOTE BUG HACK
 
 syn region onyxComment          start="//" end="$" keepend contains=onyxCommentStart
 
-syn region onyxDirective        start="#" end=" " keepend
+syn region onyxDirective        start="#" end=" "
 
 syn region onyxString              display start=+"+ skip=+\\\\\|\\"+ end=+"+ keepend
 
diff --git a/onyx b/onyx
index 4bf58d112dfa464587c19338e2baab937a0090ad..63595b46e8f882a0dceb690f5844b2561bdc3630 100755 (executable)
Binary files a/onyx and b/onyx differ
index f682285bfffbed4b0d866206388af4e940f94b67..217268ed8965323de0c790850ca4430446e195ba 100644 (file)
@@ -143,6 +143,9 @@ main :: proc (args: [] cstring) {
     r := ^s.a |> get_range() |> by(3);
     print(r);
     print_array(^s.a);
+    print("A has 22? ");
+    print(array_contains(^s.a, 22));
+    print("\n");
 
     // NOTE: Iterating by value
     for vec: s.c {
index bddcf32e0c9d4486c727a2bed4296212b3aadac0..4e8fb945b5a6bd2b6d0b6942a6abf89c4f1abdcf 100644 (file)
@@ -564,7 +564,7 @@ EMIT_FUNC(store_instruction, Type* type, u32 offset) {
     i32 store_size  = type_size_of(type);
     i32 is_basic    = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer;
     i32 is_pointer  = is_basic && (type->Basic.flags & Basic_Flag_Pointer);
-    i32 is_integer  = is_basic && (type->Basic.flags & Basic_Flag_Integer);
+    i32 is_integer  = is_basic && ((type->Basic.flags & Basic_Flag_Integer) || (type->Basic.flags & Basic_Flag_Boolean));
     i32 is_float    = is_basic && (type->Basic.flags & Basic_Flag_Float);
 
     if (is_pointer) {
@@ -615,7 +615,7 @@ EMIT_FUNC(load_instruction, Type* type, u32 offset) {
     i32 load_size   = type_size_of(type);
     i32 is_basic    = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer;
     i32 is_pointer  = is_basic && (type->Basic.flags & Basic_Flag_Pointer);
-    i32 is_integer  = is_basic && (type->Basic.flags & Basic_Flag_Integer);
+    i32 is_integer  = is_basic && ((type->Basic.flags & Basic_Flag_Integer) || (type->Basic.flags & Basic_Flag_Boolean));
     i32 is_float    = is_basic && (type->Basic.flags & Basic_Flag_Float);
     i32 is_unsigned = is_basic && (type->Basic.flags & Basic_Flag_Unsigned);