From 4ac7433c289310fc730608dce61b808369131769 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Fri, 17 Feb 2023 12:11:03 -0600 Subject: [PATCH] starting to update examples --- examples/01_hello_world.onyx | 83 +++++++++++++----------------------- examples/02_variables.onyx | 44 ++++++++++++------- examples/03_basics.onyx | 56 ++++++++++-------------- 3 files changed, 81 insertions(+), 102 deletions(-) diff --git a/examples/01_hello_world.onyx b/examples/01_hello_world.onyx index 44691db8..67e41eb1 100644 --- a/examples/01_hello_world.onyx +++ b/examples/01_hello_world.onyx @@ -1,60 +1,37 @@ -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/'. -// -// 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"); -} diff --git a/examples/02_variables.onyx b/examples/02_variables.onyx index 2160c4b3..813f2d98 100644 --- a/examples/02_variables.onyx +++ b/examples/02_variables.onyx @@ -1,24 +1,36 @@ -// 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); } diff --git a/examples/03_basics.onyx b/examples/03_basics.onyx index 58e5275f..f53c1d6b 100644 --- a/examples/03_basics.onyx +++ b/examples/03_basics.onyx @@ -1,9 +1,5 @@ // 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 @@ -22,22 +18,16 @@ main :: (args: [] cstr) { // 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 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 @@ -51,8 +41,8 @@ main :: (args: [] cstr) { // 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] , where N is a compile time know integer. @@ -67,7 +57,7 @@ main :: (args: [] cstr) { 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 @@ -79,7 +69,7 @@ main :: (args: [] cstr) { 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, @@ -92,8 +82,7 @@ main :: (args: [] cstr) { // 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 @@ -101,14 +90,14 @@ main :: (args: [] cstr) { // 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); @@ -129,17 +118,18 @@ main :: (args: [] cstr) { 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 @@ -154,13 +144,13 @@ main :: (args: [] cstr) { // 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 @@ -171,7 +161,7 @@ main :: (args: [] cstr) { // 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; -- 2.25.1