From: Brendan Hansen Date: Sat, 5 Sep 2020 20:53:20 +0000 (-0500) Subject: small bugfixes; started writing example programs with docs X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=ea9f83a749138c84c18f45789a08b641907181f8;p=onyx.git small bugfixes; started writing example programs with docs --- diff --git a/.vimspector.json b/.vimspector.json index 899b511c..2e8fdc8b 100644 --- a/.vimspector.json +++ b/.vimspector.json @@ -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": [], diff --git a/core/array.onyx b/core/array.onyx index 0d314deb..d6a58069 100644 --- a/core/array.onyx +++ b/core/array.onyx @@ -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); } diff --git a/core/stdio.onyx b/core/stdio.onyx index f4c4cd6f..e2cfac11 100644 --- a/core/stdio.onyx +++ b/core/stdio.onyx @@ -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(); } diff --git a/docs/plan b/docs/plan index 8b0c0b62..d11718c2 100644 --- 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 index 00000000..4e1e7e68 --- /dev/null +++ b/examples/01_hello_world.onyx @@ -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/'. +// +// 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 index 00000000..253c7fb7 --- /dev/null +++ b/examples/02_variables.onyx @@ -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 index 00000000..0ce0ecbd --- /dev/null +++ b/examples/03_basics.onyx @@ -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 ^. This is read as 'a pointer to a '. + // 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] , where N is a compile time know integer. + // * Slices, written [] . + // * Dynamic arrays, written [..] . + // + // 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; + } +} diff --git a/misc/onyx.vim b/misc/onyx.vim index eceb008a..1f1c588e 100644 --- a/misc/onyx.vim +++ b/misc/onyx.vim @@ -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 4bf58d11..63595b46 100755 Binary files a/onyx and b/onyx differ diff --git a/progs/poly_test.onyx b/progs/poly_test.onyx index f682285b..217268ed 100644 --- a/progs/poly_test.onyx +++ b/progs/poly_test.onyx @@ -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 { diff --git a/src/onyxwasm.c b/src/onyxwasm.c index bddcf32e..4e8fb945 100644 --- a/src/onyxwasm.c +++ b/src/onyxwasm.c @@ -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);