Tutorial:Beginner's tutorial
From Bennu Wiki
This Beginner's tutorial aims to help beginning programmers to get to know Bennu. In its current state, the tutorial is reasonably advanced already, so if you don't understand something, please leave a note. Any comments or suggestions are also welcome.
Contents |
[edit] The Beginning
Bennu, like every compiled language, has a compiler and an interpreter. The first one is used to convert the source file, the code that you write, to a file that the interpreter can understand. In this tutorial we are going to concentrate on the source file and move on from there.
[edit] Source File
A source file is simply a text file that respects certain syntax. This file can be created in any text editor, like the windows notepad. Bennu does not have an official IDE but there are patches and tutorials to make some free IDEs ready for Bennu (see Setting up Bennu).
For the sake of standardization there a few file extensions used primarily for Bennu sourcefiles:
- .prg: program code
- .inc: included code; code that is not meant to run stand-alone; header file (compare with C's .h).
Here's a basic source file:
import "mod_say" // import the module to output text to console, using say()
Process Main() // start the definition of the main process
Begin // start the code
say("Hello World!");
End // end the definition of the main process
Used in example: mod_say, import, Process, Begin, End, say()
From Hello World
[edit] Introduction to processes
Like many other languages, Bennu has functions. Functions can be seen as parts of code you can run with only a single statement (the function call). The caller will wait for the function to finish. Let's clarify this with an example.
import "mod_say" // import the module to output text to console, using say()
Function int SayHelloWorld() // start the definition of a function called SayHelloWorld
Begin // start the code
say("Hello World!");
End // end the definition of the function SayHelloWorld
Process Main() // start the definition of the main process
Begin // start the code
SayHelloWorld();
say("Hello again!"); // AFTER SayHelloWorld() is done
End // end the definition of the main process
Used in example: mod_say, import, Function, Process, Begin, End, say()
Here, we see the caller (Main and the called function SayHelloWorld).
Now you may be wondering why there are functions and processes. Processes are almost the same as functions, but with a slight difference we will discuss shortly. Try to run the same code, but making SayHelloWorld a process:
import "mod_say" // import the module to output text to console, using say()
Process int SayHelloWorld() // start the definition of a function called SayHelloWorld
Begin // start the code
say("Hello World!");
End // end the definition of the function SayHelloWorld
Process Main() // start the definition of the main process
Begin // start the code
SayHelloWorld();
say("Hello again!"); // AFTER SayHelloWorld() is done
End // end the definition of the main process
Used in example: mod_say, import, Process, Begin, End, say()
You will notice it doesn't make a difference here.
[edit] More about processes
Bennu has a unique way of programming, because it makes use of processes and frames. Frames can be seen as a way of controlling the speed of your application. For example, you can tell Bennu to run at 60 frames per second, 100 frames per second or even 1000 frames per second. Of course, if the machine can't handle it, 1000 FPS won't be reached. In each frame, every process performs its code and advances a frame.
To have processes run in parallel, just call it like you would a function. When a process is called, it causes the caller to wait for the process, also like a function. However, when the process reaches a frame; statement, it tells the caller it can continue and this is where the parallelism begins. It is important to note that it is not defined in what order the processes will be run after this. In most cases it is the order in which the processes were started, but that is not guarenteerd. Let's see this in action.
import "mod_say"
Process int SayHelloWorld()
Begin
say("SayHelloWorld: 1"); // this gets run in the first frame
frame; // wait for the second frame to start
say("SayHelloWorld: 2"); // this one in the second frame
frame; // wait for the third frame to start
say("SayHelloWorld: 3"); // this one in the third frame
End
Process Main()
Begin
SayHelloWorld();
say("Main: 1"); // this gets run in the first frame and AFTER the first one SayHelloWorld
frame; // wait for the second frame to start
say("Main: 2"); // this one in the second frame, BEFORE the one in SayHelloWorld
frame; // wait for the third frame to start
say("Main: 3"); // this one in the third frame, BEFORE the one in SayHelloWorld
End
The output is:
SayHelloWorld: 1 Main: 1 Main: 2 SayHelloWorld: 2 Main: 3 SayHelloWorld: 3
In the first frame, Main waits for SayHelloWorld, so the say() in SayHelloWorld is executed before the one in Main. After that, the processes run in turns, in this case Maingets his turn before SayHelloWorld. However, it is possible to influence the order of execution, but more on that later.
[edit] Frames
Bennu's interpretation works with frames. You can limit the execution rate by limiting the allowed amount of frames per second. To tell Bennu a process is done for the current frame, the statement frame; can be used. Multiple processes will take turns in getting executed and will not be executed simultaneously. For the more advanced people reading this, this is indeed a lot like coroutines, where instead of frame, the keyword yield is used; In Bennu you can also use yield as you would use frame.
import "mod_say"
import "mod_time" // import a time module, for get_timer(), which returns the number
// of milliseconds after the program was started
Process Main()
Begin
while(get_timer()<1000)
say("Hello World! @" + get_timer());
frame;
end
End
Used in example: mod_say, mod_time, while, frame, say(), get_timer()
This example will output Hello World! @... as many times as possible in one second.
But suppose we were to limit the FPS to 25, then it would only output it 25 times, spread evenly over the second it ran. This would be pretty useful in cases. Currently, this is done by importing mod_video and using set_fps(). This means that each Bennu-frame the mod_video module 'tells' Bennu to wait until the next frame should start. For example if the FPS is 25, the time between the start of frames is 40ms. If the execution of Bennu took 10ms, mod_video will tell Bennu to wait 30ms with execution, making sure the FPS is correct.
[edit] Processes and functions
One of the key features of Bennu is the use of processes. The code in each process runs independently from each other, although never at the same time, but the execution rate is influenced by the frame statement. A frame-statement in a process says something like "this process is done for this frame" and the process will wait (at the frame-statement) for the next frame to begin and the process acquires running privileges again.
A function is almost the same, but not entirely independent. The process or function calling a function will wait for the function to return even if that function contains a frame-statement. A good example is the textinput tutorial.
Processes and functions are given a unique identification number when they are created. You can use this to manipulate its execution (using signal), access local variables and its public variables (using the . operator). We will discuss more on this later.
Now let's create another process:
import "mod_say"
import "mod_time"
import "mod_proc" // import a process manipulator module, for let_me_alone()
Process Another() // start the definition of another process
Begin // start the code
Loop // endlessly say something
say(get_timer() + " - Another");
frame;
End
End // end the definition of another process
Process Main()
Begin
Another(); // start another process
while(get_timer()<1000)
say(get_timer() + " - Main");
frame;
end
let_me_alone(); // kill ALL processes EXCEPT this one
End
Used in example: mod_say, mod_time, mod_proc, say(), get_timer(), let_me_alone()
Notice that Main and Another are executed one after the other. If we were to limit the FPS to 25, they both would run at 25FPS, one after the other.
[edit] Multiple source files
If you want to use multiple sources files - which you want for larger projects, you are able to accomplish this by using the include statement.
Consider the last example. We'll cut it up in a .prg and a .inc.
example.prg:
import "mod_say"
import "mod_time"
import "mod_proc"
include "example.inc" // include example.inc: it will be as if the contents of
// example.inc were located at this point in example.prg
Process Main()
Begin
Another();
while(get_timer()<1000)
say(get_timer() + " - Main");
frame;
end
let_me_alone();
End
example.inc:
import "mod_say" // we use these modules in this file, so it is good practise
import "mod_time" // to (also) include them here
Process Another()
Begin
Loop
say(get_timer() + " - Another");
frame;
End
End
[edit] Variables and datatypes
[edit] Variables
[edit] Basics
There are multiple scopes of variables in Bennu:
- Global variables are variables available throughout the code (from the point of declaration)
- Constants are like global variables, but their value cannot be changed.
- Private variables are variables only available to the process or function in which they were declared.
- Public variables are like private variables, but they can also be accessed outside of the owning process.
- Local variables are like public variables, but they apply to all processes instead of a single one.
Bennu has various default types of variables:
| type | - can contain |
| int (32bits) | - -2147483648..2147483647 (only whole numbers)
|
| dword (unsigned int) | - 0..4294967295 (only whole numbers)
|
| short (16bits) | - -32768..32767 (only whole numbers)
|
| word (unsigned short) | - 0..65535 (only whole numbers)
|
| char (8bits) | - -127..128 (only whole numbers)
|
| byte (unsigned char) | - 0..255 (only whole numbers)
|
| float (32bits float) | - almost any real number
|
| string | - text |
To declare global variables, use Global:
Global
int i;
float f;
End
To declare constants, use Const. Notice that there is not a datatype declared:
Const
i = 0;
f = 0.0;
End
To declare private variables, use Private inside a process or function definition:
Process myprocess()
Private
int i;
float f;
End
Begin
End
To declare public variables, use Public inside a process or function definition:
Process myprocess()
Public
int i;
float f;
End
Begin
End
To declare local variables, use Local:
Local
int i;
float f;
End
[edit] Arrays, Structs and pointers
We can also use the basic types to form arrays, structs and pointers. Arrays are formed using brackets:
Global // or somewhere else it is allowed to declare variables
int my_array_of_ten_integers[9]; // this makes an array of ten integers 0..9
End
We can access these integers like this:
my_int = my_array_of_ten_integers[0]; my_index = 9; my_int = my_array_of_ten_integers[my_index];
Structs are formed using the keyword struct:
Global // or somewhere else it is allowed to declare variables
Struct MyStruct // makes a struct with two members
int i;
float f;
End
End
We can access members by using the [[.]] operator:
my_int = MyStruct.i; my_float = MyStruct.f;
We can also make an array of structs:
Global // or somewhere else it is allowed to declare variables
Struct MyStruct[9] // makes ten structs
int i;
float f;
End
End
Pointers are variables which can 'point' to variables. They actually hold the address where that variable is located. If for some reason you move the variable (maybe because you used realloc()), the pointer no longer points to the variable. This is important to understand: the pointer points to a memory address. The operators * and & are used when dealing with pointers:
import "mod_say"
Global
int my_int;
int* my_int_pointer;
End
Process Main()
Begin
my_int = 4;
my_int_pointer = &my_int; // the & operator returns the address of the variable
say("my_int: " + my_int);
say("my_int_pointer: " + my_int_pointer);
say("*my_int_pointer: " + *my_int_pointer); // the * operator dereferences the variable and returns the contents
End
Output:
my_int: 4 my_int_pointer: 003FD134 *my_int_pointer: 4
So you see how you can use pointers. If you don't understand them yet or don't see their use, just forget about them for now, but remember they are there for when you need them.
[edit] Usermade datatypes
Making your own types can be useful at times. In Bennu, your own types are structs, meaning they can hold multiple variables. You can read more about it here. A quick example:
Type Point
float x;
float y;
End
Global
Point A,B;
End
Process Main()
Begin
A.x = 0;
A.y = 1;
B.x = A.x;
B.y = 6;
End
[edit] ProcessID, Public and Local
A process or function returns its ID when it reaches a frame-statement. We can assign this to a variable and use it later on.
We could rewrite the earlier example to this:
import "mod_say"
import "mod_time"
import "mod_proc" // import a process manipulator module, for signal()
Process Another()
Begin
Loop
say(get_timer() + " - Another");
frame;
End
End
Process Main()
Private
Another a;
int b;
End
Begin
a = Another(); // start another process
b = Another(); // start another process
while(get_timer()<1000)
say(get_timer() + " - Main");
frame;
end
signal(a,S_KILL); // kill a
signal(b,S_KILL); // kill b
End
You might wonder what the difference is between Another a and int b. The difference is that when you make a variable with the datatype the name of a process, Bennu can use this information to address public variables. Bennu does not know the type of process b is holding, so it can't access its public variables, because it can't know it has any. Locals however apply to all processes, so they can be accessed with both a and b.
Consider:
import "mod_say"
import "mod_time"
import "mod_proc"
Local
int loc = 1;
End
Process Another()
Public
int pub = 2;
Begin
Loop
frame;
End
End
Process Main()
Private
Another a;
int b;
Begin
a = Another(); // start another process
b = Another(); // start another process
say("a.loc = " + a.loc);
say("b.loc = " + b.loc);
say("a.pub = " + a.pub);
//say("b.pub = " + b.pub); // not possible
signal(a,S_KILL); // kill a
signal(b,S_KILL); // kill b
End
[edit] Moving Up
[edit] Arguments, Parameters and Return
You can pass processes and functions information using parameters. They can also return a value. When the process or function reaches a frame-statement, its processID is returned, which can be saved in a variable, or otherwise used. If it reaches a return statement first, then it will return the value supplied with the return statement. If return; is used, or the process or function reaches neither a return-statement or a frame-statement, the ID is also returned. This sums up to: a process or function returns its ID, unless otherwise specified.
For example:
import "mod_say"
Process Main()
Begin
say("multiply(3,4) = " + multiply(3,4));
End
Function int multiply(int a, int b)
Begin
return a*b;
End
And:
import "mod_say"
import "mod_proc"
Process Main()
Begin
say("an object with ID: " + object());
say("an object with ID: " + object());
say("an object with ID: " + object());
let_me_alone();
End
Process object()
Begin
Loop
frame;
End
End
[edit] Declare
Sometimes, you want to know how a process is declared before it is defined. When you declare something, it is not yet defined, but if you define something, it is declared. It is good practice to only use things that are declared when used, otherwise you could get lost by tracing weird bugs you caused. But what if you want to call process A inside process B and the other way around? You would have to declare both processes beforehand, using Declare:
Declare Process A()
End
Declare Process B()
End
Process A()
Begin
B();
End
Process B()
Begin
A();
End
You may have already noticed that this code is pretty bad: A will call B, B will call A, A will call B, etc.
You can use Declare for private variables and public variables too:
Declare Process A()
Public
int i;
End
End
Declare Process B()
Public
float f;
End
End
Process A()
Private
B b;
Begin
b = B();
b.f = 5;
End
Process B()
Begin
f = 1;
say("f: " + f);
frame; // let A continue
say("f: " + f);
End
[edit] Strings
Strings are a special kind of datatype. They could be described as a dynamic array of characters, meaning that the length of the characters can vary. Strings are mostly used for holding text.
Const
MSG_ERROR = "Oops, error occured!";
MSG_SUCCESS = "Success!";
End
Process Main()
Private
int error; // doesn't really do anything
Begin
if(error)
say(MSG_ERROR);
else
say(MSG_SUCCESS);
end
End
Used in example: string, say()
Of course, you can do stuff with strings too. Let a and b be strings: a + b = the concatenation of a and b ("ab"+"cd"="abcd"). More functionality for strings can be found in mod_string.
[edit] Goto Labels
[edit] Further reading
language:
- basic statements
modules:
- angle,x,y,z,graph,pi etc with processes (make this article!)
- fpg,fgc: graphics libraries (make this article!)
- blendops (this one is somewhere)
- graphics article (make this article!)
- text
- window manager
actually every module should have an article about it, telling what's it for

