-package main
+// This is a (heavily commented) "Hello, World!" program for Onyx. It explains
+// the very basics of an Onyx program.
+
// 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.
+// So, the package specification below is redudant, but is there for sake of completeness.
+// We will talk about the package system more later.
+package main
-// 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.
+// By default, the core standard libraries are included in any Onyx program. When
+// designing the language, there wer two choices: include the standard libraries
+// by default, or have the user include them. As the vast majority of Onyx programs
+// use the standard library, I opted to include them by default. There is a compiler
+// option to disable loading the standard library, if that is needed. For the sake
+// of completeness, the following line manually includes the standard library, but
+// this is not necessary.
#load "core/std"
-// 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 file 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 file 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 file 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
+// Below is the function declaration for `main`. While it might look weird at first
+// sight, Onyx uses a consistent syntax for declaring static "things" (functions,
+// structures, enums, etc.). It is always,
+//
+// the_name :: the_thing
+//
+// In Onyx a function is simply parentheses enclosing the arguments, an optional
+// return type, then the body. So below, `main` is an alias for a function that
+// takes no parameters, and simply prints "Hello, World!". You will see that this
+// is the same syntax for just about everything else in the language.
+//
+// `main`, like most programming languages, is the entry point for an Onyx program.
+// In Onyx, `main` can either take 0 or 1 parameter(s), that being the command-line
+// arguments passed. In programs that do not need them, they can simply be omitted.
+main :: () {
+ core.println("Hello, World!");
+}
-// 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 cstr's, which were the arguments passed from the command line. We will talk about
-// slices later.
-main :: (args: [] cstr) {
- // 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");
-}
-// 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.
+// Notice this time, we are not adding, 'package main' or '#load "core/std"'
+// to the top of the file, since every file is automatically part of the
+// main package unless specified otherwise, and every compilations includes
+// the standard library.
-#load "core/std"
+main :: () {
-use package core
-
-main :: (args: [] cstr) {
-
- // This is the syntax for declaring a local variable in a procedure.
+ // To declare a local variable, the variable name is simply followed by
+ // a single ':', and then the type of the variable is given. In this case,
+ // `foo` is an `i32`, which is Onyx's 32-bit signed integer type. It is
+ // then immediately assigned the value of 10.
foo: i32;
foo = 10;
- // You don't have to put the declaration and assignment on different lines.
- footwo: i32 = 10;
+ // You can combine the two lines above into one, just as you would expected.
+ // This declares `bar` to be an `i32`, and assigns it the value 10.
+ bar: 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;
+ // As a MAJOR convenience, you can omit the type of a variable, if it can
+ // be determined by the type of the right hand side expression. In this
+ // line, the integer `15` defaults to the type `i32`. This type is then
+ // used as the type of `qux`. This may not seem super useful yet, but
+ // when there are very compilated types in your program, this saves
+ // a lot of typing and _arguably_ makes the code more readable.
+ qux := 15;
- printf("foo is {}\n", foo);
- printf("footwo is {}\n", footwo);
- printf("bar is {}\n", bar);
+ //
+ // As an aside, the `printf` function in the `core` package is used to
+ // print formatted strings. Because of a lot of language features that
+ // will be discussed later, `printf` simply uses '{}' to denote where
+ // a value should be formatted, instead of pre-typed strings, like "%s",
+ // or "%d" from C.
+ core.printf("foo is {}\n", foo);
+ core.printf("bar is {}\n", bar);
+ core.printf("qux is {}\n", qux);
}
// Now, lets go over the basic types and control flow mechanisms in Onyx.
-#load "core/std"
-
-use package core
-
main :: (args: [] cstr) {
// Here is a list of all the builtin types:
// void
// 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 try to explain what a pointer is here, but something to note is that
- // pointers in Onyx are 8-bytes in size, but only have their lower 4-bytes used,
- // since Onyx's compile target is WASM32. This way, when WASM64 is standardized,
- // pointer will not cause any issues when moving over code to 64-bit.
//
// 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 cstr builtin. It is ^u8, which is what C would call a char*.
//
- // Also, this example uses printf which will be talked about later. If you are familar
- // with the C printf, it is very similar, except %d in C is %i in this language.
foo := 10;
foo_ptr := ^foo;
- printf("foo is {}\n", foo);
- printf("foo_ptr is {}\n", foo_ptr);
+ core.printf("foo is {}\n", foo);
+ core.printf("foo_ptr is {}\n", foo_ptr);
// An important type to know before proceeding is the range type. When designing
// 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 ");
- println(rng);
+ core.print("rng is ");
+ core.println(rng);
// Onyx has 3 different array-like types:
// * Fixed-size arrays, written [N] <type>, where N is a compile time know integer.
for i: 0 .. 10 {
fixed_arr[i] = i * i;
}
- printf("fixed_arr[3] is {}\n", fixed_arr[3]);
+ core.printf("fixed_arr[3] is {}\n", fixed_arr[3]);
// A quick thing to note about fixed size arrays is that they copy their contents
// on assignment. In this example, another_arr declares space on the stack for 10
fixed_arr[3] = 1234;
- printf("another_arr[3] is {}\n", another_arr[3]);
+ core.printf("another_arr[3] is {}\n", another_arr[3]);
// Slices are a concept not unique to Onyx. They represent a pointer and a count,
// 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];
- printf("slice_arr[3] is {}\n", slice_arr[3]);
-
+ core.printf("slice_arr[3] is {}\n", slice_arr[3]);
// 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
// just how many elements are currently in the array. To facilitate using dynamic
// arrays, there are many procedures in the core.array package. Take this example.
dyn_arr : [..] i32;
- array.init(^dyn_arr);
- defer array.free(^dyn_arr);
+ core.array.init(^dyn_arr);
+ defer core.array.free(^dyn_arr);
for i: 0 .. 10 {
- array.push(^dyn_arr, i * i * i);
+ core.array.push(^dyn_arr, i * i * i);
}
- printf("dyn_arr is {}\n", dyn_arr);
+ core.printf("dyn_arr is {}\n", dyn_arr);
if_var := 5 * 2 + 3;
if if_var == 13 {
- print("if_var was 13\n");
- print("another statement here!\n");
+ core.print("if_var was 13\n");
+ core.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");
+ elseif if_var == 14 do core.print("if_var was actually 14\n");
+ else do core.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.
+ // Ifs, as well as whiles and switch statements, can have an initialization statement.
+ // This statement is run before the condition is checked, and can be used to declare
+ // variables that are 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");
+ core.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
// for i to be 11.
while i := 0; i < 10 {
- printf("{} ", i);
+ core.printf("{} ", i);
i += 1;
} else {
- print("The while loop never ran...");
+ core.print("The while loop never ran...");
}
- print("\n");
+ core.print("\n");
// Onyx also has the standard 'break' and 'continue' statement found in many other
// break or continue the loop that you want to. Take this example.
while i := 0; i < 10 {
while j := 0; j < 10 {
- printf("{} {}\n", i, j);
+ core.printf("{} {}\n", i, j);
if i == 1 && j == 2 do break break;
j += 1;