"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/onyx",
- "args": ["-verbose", "progs/poly_test.onyx"],
+ "args": ["-verbose", "examples/02_variables.onyx"],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
}
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;
}
}
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);
}
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
^print_buffer
|> string_builder_to_string()
|> system.output_string();
-
+
^print_buffer |> string_builder_clear();
}
* 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
--- /dev/null
+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");
+}
--- /dev/null
+// 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");
+}
--- /dev/null
+// 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;
+ }
+}
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
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
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 {
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) {
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);