The Genie Programming Language Tutorial

What is Genie?

Genie is a programming language, in the same vein as Vala. Genie allows for a more modern programming style while being able to effortlessly create and use GObjects natively.

The syntax is designed to be clean, clear and concise and is derived from numerous modern languages like Python, Boo, D language and Delphi. For a sample, take a look at instance.gs from the Khövsgöl project.

Genie is very similar to Vala in functionality but differs in syntax allowing the developer to use cleaner and less code to accomplish the same task.

Like Vala, Genie has the same advantages:

  • Programs written in Genie should have similar performance and resource usage to those written directly in Vala and C

  • Genie produces C code that compiles to an executable binary. This means Genie has none of the bloat and overhead that comes with many other high level languages which utilize a VM (e.g. Python, Mono, Java)
  • Classes in Genie are actually GObjects so Genie can be used for creating platform code like widgets and libraries where GObjects are required for binding to other languages.

Hello World

As Hello World is the de facto first sample program, here is Genie's in all its glory:

init
	print "Hello World"

Provided you have Vala installed, copy and paste the code above and save it in a file called "hello.gs". Genie code must be written in files with the *.gs extension.

Compile and run it by simply issuing the following on a UNIX-like command line in the same directory you saved "hello.gs":

$ valac hello.gs
$ ./hello

When you want to compile Genie code, you give the compiler a list of the files required, and the Genie/Vala compiler will work out how they fit together.

If you give valac the -C switch, it will create a C source code file called "hello.c" instead of an executable binary file.

Hello World Explained

As you can see, Genie is pretty clean and concise so the above example wont need much explaining...

init Block

An init block declared outside of a class or struct is equivalent to a "main" function in C and only one of these may be present. Any code that needs to be run at start up should be present here.

Block Indentation

Block indentation in Genie uses tabs by default. So in the Hello World example above, a tab appears prior to the keyword print.

It's also possible to tell it to use a certain number of spaces in lieu of a tab by ensuring the first line of the source file contains the following attribute:

[indent=2]
init
  print "Hello World"

That attribute tells Genie to ignore tabs and use 2 spaces to signify a block indentation. An indent=0 means use a tab.

Code examples on this page will always use [indent=4] as this wiki converts tabs to spaces in code examples. This means no tabs would be copied in a copy and paste of the code, unless the tabs had been entered as HTML entities in the wiki.

Case and Unicode

Genie is case sensitive - so be careful when mixing case in your code. Keywords are always lower case. Identifiers can be mixed case, but must be in the Unicode U+0020 to U+007E range. This is the ASCII printable characters range as a result of it being compiled into C code. For example:

init
	pi:double = Math.PI
	print pi.to_string()

would compile, but

init
	π:double = Math.PI
	print π.to_string()

would produce an error because the symbol π is outside the ASCII printable range of characters.

Genie can, however, handle Unicode data very well. Here, is an example of Hello World with Unicode characters outside the ASCII range:

init
	Intl.setlocale( LocaleCategory.ALL, "" )
	print "Hello World! 你好世界"

C libraries use the US English, ASCII encoding, by default as part of the C standard. The setlocale command changes this to the locale of the machine and if this can handle Unicode and has Chinese glyphs installed then the Chinese version of Hello World will be displayed.

Compiling and Genie's Relation to C

This section gives an overview of Genie's relation to the C programming language. There are two useful points for anyone getting to know Genie. The first is of immediate practical use and that is Genie's relation to C libraries. C libraries can be easily used in Genie when a VAPI (Vala Application Programming Interface) file exists. The second is to gain an overview of what is going on in the background when valac is run. This will help to understand compiler feedback messages, organise larger projects, develop the Genie parser itself, etc.

When the command

valac hello.gs

was issued the stages valac followed are illustrated below:

genie_and_valac.svg

In the first stage valac used the Genie parser to convert the hello.gs file into an internal representation. Note that .vala files can be given as parameters to valac at the same time, so Genie and Vala files are compiled to the same binary.

In the second stage valac checked the internal representation was meaningful code, for example that identifiers were defined, and then produced the C code.

In the third stage valac called on a C compiler. The C compiler then produced the binary from the C code. The default C compiler is gcc, but this can be altered with the --cc=COMMAND switch. If .c and .h files were also given as parameters to valac then these files will be passed to the C compiler and compiled to the same binary.

Using a C Library

Genie and Vala maintain a high level of compatibility with C programs and libraries. This is a powerful feature because it allows you to select from many efficient and well tested C libraries to use in your Genie code. A list of bindings is maintained on the Vala wiki.

A translation file is used by the Vala compiler to translate the Genie or Vala syntax into the correct C code for use with a specific C library. A translation file is called a VAPI (Vala Application Programming Interface) file. By default valac includes two VAPI files when it compiles a Genie program. These are for GLib and GObject. GLib is a cross platform general purpose utility library that works on many Unix-like platforms, Windows and Mac OS X. GLib provides things like data types, type conversions, string utilities, file utilities, a mainloop abstraction, etc. for Genie. GObject is part of GLib and provides Genie's object system.

To use a C library three things need to be done. Firstly install the library and its development files. Secondly use the --pkg switch when calling valac. Then finally use the library with its namespace. For example Genie's list and dictionary collection types are provided by libgee. To use libgee you will need to install the library. Its development files may be distributed separately, called something like libgee-devel or libgee-0.8-dev. To compile a Genie program that uses libgee:

valac --pkg gee-0.8 program.gs

The --pkg gee-0.8 tells valac to look for a VAPI file called gee-0.8.vapi and also a pkg-config file called gee-0.8.pc. The 0.8 signifies the version of the API. pkg-config files provide information on where the library files are stored on the system so they can be given automatically to the C compiler. Each library needs to be given as a separate --pkg switch:

valac --pkg gee-0.8 --pkg gio-2.0 program.gs

The VAPI will provide a namespace for the library. This is often slightly different to the VAPI filename. To use libgee without prefixing everything with Gee use the following Genie command:

uses Gee

The namespace GLib is automatically included in Genie.

libgee is written in Vala and could be a useful example of how to write a Vala or Genie program that produces a shared library and C header interface. See libgee source code.

A Simple Build Script

A project will usually be made up of multiple source files and libraries. It is often easier to manage these with a build system. For a personal project, of up to a few thousand lines of code, a single script is usually enough. By placing the source files and libraries over multiple lines in this simple script it helps to add and remove entries as the project evolves. The script would also use the --output switch in valac to produce a single named binary.

For Unix-like platforms, such as GNU/Linux and OS X, an example script would be:

#!/bin/sh
valac \
        src/init.gs \
        src/CLIOptions.gs \
        src/Configuration.gs \
        src/Logging.gs \
        src/devices/DeviceFactory.gs \
        src/devices/NoDevice.gs \
        src/devices/BlockDevice.gs \
        src/devices/FileAsDevice.gs \
        --pkg gio-2.0 \
        --pkg gee-0.8 \
        --output exampleprogram

For Windows this can be done with a .bat file:

valac src/init.gs ^
 src/CLIOptions.gs ^
 src/Configuration.gs ^
 --pkg gee-0.8 ^
 --output exampleprogram

Compile Time Checks

The Vala compiler will run a number of checks on the Genie code. This is called static code analysis. One of the main checks carried out is to make sure data types match. This code:

init
	a:int = 1
	b:double = 3.5
	c:int = a + b
	print c.to_string()

will produce a compile time error similar to this:

type_error.gs:4.8-4.15: error: Assignment: Cannot convert from `double' to `int'
        c:int = a + b
              ^^^^^^^^

The message identifies the source file containing the error, type_error.gs, and the line number, 4. The characters at positions 8 to 15 on the line are identified as the error and these are also highlighted in the line below.

Example C Output

Usually valac will remove the C files produced after the binary has been compiled. The temporary C files can be kept by using the --save-temps switch. To produce just the C files use the --ccode switch. For example:

valac --ccode hello.gs

will produce a file, hello.c, similar to this:

/* hello.c generated by valac 0.29.1, the Vala compiler
 * generated from hello.gs, do not modify */

#include <glib.h>
#include <glib-object.h>
#include <stdlib.h>
#include <string.h>

void _vala_main (gchar** args, int args_length1);

void _vala_main (gchar** args, int args_length1) {
        g_print ("Hello World\n");
}

int main (int argc, char ** argv) {
#if !GLIB_CHECK_VERSION (2,35,0)
        g_type_init ();
#endif
        _vala_main (argv, argc);
        return 0;
}

There is no need to learn C programming when using Genie, but understanding the process valac follows will help understand some of the messages you may encounter when compiling a Genie program. If you want to examine more deeply how Genie works you can read from the example above that the Genie print command is translated into GLib's g_print () function call.

Runtime Analysis

For testing GLib has a test framework.

There are a number of sophisticated tools for analysing binaries produced from C code. valgrind is a good tool for checking memory use and the GNU debugger, gdb, allows controlled execution of a binary. To embed Genie line numbers in the test binary use the --debug switch with valac. In most cases, however, these tools will not be needed. Genie uses reference counting for memory management. In some data structures it is possible to create reference cycles and the memory is not freed. See Vala's Memory Management Explained for more details. Memory leaks may also occur when a binding to a C library has not defined ownership correctly or there is a bug in the C library.

Technical Documentation

Documentation of code can be a burden and get out of step with the current code. These are arguments in favour of self-documenting code.

Naming Conventions

Identifiers have to be use the ASCII character set to produce compatible C code. That is the main rule about identifiers. Naming conventions are followed in the GLib programming world that are useful to know and follow:

StyleUsed withExample
lower_snake_case Functions / Methods
def function_name ()
	pass
UpperCamelCaseClasses
class ClassExample
Enums
enum EnumExample
Namespaces
namespace NamespaceExample
UPPER_SNAKE_CASEEnum values
enum EnumExample
	FIRST_VALUE
	SECOND_VALUE

Comments

Genie allows comments in code in two different ways.

// Comment continues until end of line

/* Comment lasts between delimiters */

Identifiers

An identifier is defined by its name and its type, e.g. i:int meaning an integer called i. In the case of value types this also creates an object of the given type. For reference types these just defines a new reference that doesn't initially point to anything.

Reference types are instantiated using the new operator and the name of a construction method, which is usually just the name of the type, e.g. var o = new Object() creates a new Object and makes o a reference to it.

Prefixing symbols with @ let you using reserved keywords. This is mainly useful for bindings. You can use an argument @for for example.

Data Values

Numbers

Whole Numbers

Whole numbers are represented by the integer type:

a:int = 1

This should cover most use cases when starting with Genie, but you can also set the width of the integer and make it unsigned:

init
	a:int = 1
	b:int8 = 2
	c:int16 = 3
	d:int32 = 4
	e:int64 = 5
	print "%i, %i, %i, %i, %lli", a, b, c, d, e

	f:uint = 6
	g:uint8 = 7
	h:uint16 = 8
	i:uint32 = 9
	j:uint64 = 10
	print "%u, %u, %u, %u, %llu", f, g, h, i, j

Real Numbers

Real numbers are represented by the double-precision floating point number type:

a:double = 0.5

This is C implementation specific, but will usually be IEEE 754 double-precision floating-point format.

C's "Usual Arithmetic Conversions"

Text

Text is stored in the string type and delimited between double quotes:

a:string = "Some text"

Multi-line text can be delimited between three double quotes:

a:string = """
Multi-line text
Examples of where this is useful are SQL and text menus"""

This is called a verbatim string.

Single quotes delimit a single character:

a:char = 'a'

Booleans

A boolean stores either true or false:

a:bool = true

Enumerations and Flags

The following code example defines an enum type. This is an enumerable sequence of values starting from zero. In this example FIRST_VALUE has the value 0 and SECOND_VALUE has the value 1:

enum MyEnum
	FIRST_VALUE
	SECOND_VALUE

It is possible to give a specific integer value to any entity in an enum:

enum MyEnum
	FIRST_VALUE = 1
	THIRD_VALUE = 3

An enum value is referred to by its type and identifier, e.g. MyEnum.FIRST_VALUE.

The [Flags] attribute marks an enum as a set of binary digits (bits). In this example Colors.RED is 1 (001 in binary), Colors.GREEN is 2 (010) and Colors.BLUE is 4 (100). Colors.YELLOW is 3 (011), a combination of RED and GREEN:

[Flags]
enum Colors
	RED
	GREEN
	BLUE
	YELLOW = RED | GREEN
	MAGENTA = RED | BLUE
	CYAN = GREEN | BLUE

Dates and Times

GLib provides classes for handling dates and times. In Valadoc see GLib.DateTime, GLib.Date and GLib.Time

Operators

/* assignment */
a = 2

/* arithmetic */

a = 2+2         // 4
a = 2-2         // 0
a = 2/2         // 1
a = 2*2         // 4
a = 2%2         // 0


a++     // equivalent to a = a + 1
a--     // equivalent to a = a - 1
a += 2  // equivalent to a = a + 2
a -= 2
a /= 2
a *= 2
a %= 2

/* relational */

a > b
a < b
a >= b
a <= b
a is not b // not equal eg if a is not 2 do print "a is not 2"
a is b // equality eg  if a is 2 do print "a is 2" 

/* logical */
a and b
a or b
not (b)

if (a > 2) and (b < 2)
    print "a is greater than 2 and b is less than 2"

/* bitwise operators (same as C syntax) */
|, ^, &, ~, |=, &=, ^=

/* bit shifting */
<<=, >>=

/* Object related */
obj isa Class

Control Flow Statements

All control flow statements can take blocks or be single line. Single line ones must always use the do keyword between the condition and the statements to be executed.

if, while and for may be controlled with the keywords break and continue. A break instruction will cause the loop to immediately terminate, while continue will jump straight to the next part of the iteration.

Conditional Execution with 'if'

if a > 0 
	print "a is greater than 0"
else if a is 0 
	print "a is equal to 0"
else 
	print "a is less than 0"

executes a particular piece of code based on a set of conditions. The first condition to match decides which code will execute, if a is greater than 0 it will not be tested whether it is less than 0. Any number of else if blocks is allowed, and zero or one else blocks.

if a > 0 do print "a is greater than 0"

Conditional Loops with 'while'

while a > b 
	a--

while a > b do a--

will decrement "a" repeatedly, checking before each iteration that "a" is greater than "b".

count:int = 1
do
	print "%i", count
	count++
while count <= 10

will always print at least one value of count

The 'for' Loop

for var i = 1 to 10 
	print "i is %d", i

will initialize "i" to 1 and then call the print statement repeatedly whilst incrementing "i" until it reaches a value of 10

for s in args do print s

will print out each element in the args string array or another iterable collection. The meaning of iterable will be described later.

'case ... when'

case a 
	when 0,1,2 
		print "a is less than 3"
	when 3 
		print "a is 3"
	default 
		print "a is greater then 3"

a case...when statement runs a section of code based on the value passed to it. In Genie (unlike C) there is no fall through between cases, as soon as a match is found no more will be checked.

Genie case...when statements work with string variables, too

var s = "winter"
case s
	when "winter"
		print "its cold"
	when "summer"
		print "its warm"
	default 
		print "its warm and cold"

Scope of Identifiers

Block Level Scope

Genie uses meaningful white space to show blocks of code. This makes it easy to identify a block of code. When an identifier is declared it can only be accessed in the block of code it is declared. Attempting to access the identifier outside of that block will result in an error from the Vala compiler.

A variable is an identifier that identifies an instance of a data type. A variable is declared with its label, a colon and its data type, e.g. a:string or b:int. It is good practise to also initialize the variable by assigning a value to the variable at the same time, e.g. a:string = "example string" or b:int = 0. This avoids an error if you try to read an uninitialized variable.

An advantage of having an identifier only accessible in the block it is declared is that the identifier can be re-used without a name clash. For example:

init
	if true
		a:string = "test"
		print a
	if true
		a:string = "another"
		print a

If you change the second declaration of a to be just an assignment, that is a = "another", then the Vala compiler will produce an error stating a does not exist. This is because the variable only exists in the conditional block of the if true statement. To make the variable available in the init block use:

init
	a:string = ""
	if true
		a = "test"
		print a
	if true
		a = "another"
		print a

This shows the variable is accessible in the block it is declared and any sub-blocks.

The advantage of not having clashing names is clearer when applied to functions:

init
	first_example_function()
	second_example_function()
    
def first_example_function()
	first_value:int = 1
	second_value:int = 2
	answer:int = first_value + second_value
	print "%i + %i = %i", first_value, second_value, answer

def second_example_function()
	first_value:int = 100
	second_value:int = 200
	answer:int = first_value * second_value
	print "%i * %i = %i", first_value, second_value, answer

Here the working variables, such as answer, do not clash. This makes it easier to write a block of code because no checks need to be made by the programmer that the identifier has already been used.

Global Scope

Identifiers that are available in all blocks of the program, the global scope, should generally be avoided because they make it hard to test code. Writeable global identifiers introduce uncertainty into the program because it becomes unclear where the values are altered.

Namespaces

namespace MyNameSpace
   blah...

everything within the indent of this statement is in the namespace "MyNameSpace" and must be referenced as such. Any code outside this namespace must be either used qualified names for anything within the name of the namespace, or be in a file with an appropriate using declaration. Namespaces may contain any code except using statements.

Namespaces can be nested, either by nesting one declaration inside another, or by giving a name of the form "NameSpace1.NameSpace2".

Several other types of definition can declare themselves to be inside a namespace by following the same naming convention, e.g. class NameSpace1.Test.... Note than when doing this, the final namespace of the definition will be the one the declaration is nested in plus the namespaces declared in the definition.

To use a namespace, use the uses statement:

uses
    Gtk

Access Modifiers

Identifiers within objects can be marked as private or public. A private identifier is only accessible within the block scope it is identified in the object. A public identifier can also be accessed within the block scope the object was created.

Scope Based Resource Management

The final block of an object is called when the object goes out of scope. This can be used to release any resources claimed by the object.

Identifiers and Type

Function Signatures (Delegates)

delegate DelegateType(a:int)

a delegate is a type of function pointer, allowing chunks of code to be passed around like objects. The example above defines a delegate called "DelegateType" which represents a function taking an int and not returning a value. Any function with such a signature may be used as a "delegate_name" as shown in the following sample:

[indent=4]

delegate DelegateType (a : int) : bool
        
def f1 (a:int) : bool
    print "testing delegate value %d", a
    return true
                
def f2 (d:DelegateType, a:int)
    var c = d (a)

// to execute it 
init
    f2 (f1, 5)

this code will execute the function "f2", passing in a pointer to function "f1" and the number 5. "f2" will then execute the function "f1", passing it the number.

Delegates may also be created locally. A member method can also be assigned to a delegate. Here is an example of such usage:

class Test : Object
    data : int = 5
    def method (a:int)
        print "%d %d", a, data

delegate DelegateType (a : int)

init
    var t = new Test()
    d : DelegateType = t.method
    d(1)

Using a delegate within a class is quite similar to its usage in a namespace. It is important, however, to utilize the constructor to avoid "assertion self != null failed" run-time errors. An example of delegate usage within a class:

class Test : Object
    delegate DelegateType (a : int)
    data : int = 5
    d : DelegateType

    construct()
        self.d = method

    def method (b:int)
        print "%d %d", b, data

    def run (c:int)
        d(c)

init
    var t = new Test()
    t.run(1)

Classes

Classes allow new data types to be defined.

When a class is instantiated it is called an object.

Interfaces

A class in Genie may implement any number of interfaces. Each interface is a type, much like a class, but one that cannot be instantiated. By "implementing" one or more interfaces, a class may declare that its instances are also instances of the interface, and therefore may be used in any situation where an instance of that interface is expected.

The procedure for implementing an interface is the same as for inheriting from classes with abstract methods in - if the class is to be useful it must provide implementations for all methods that are described but not yet implemented.

Interfaces in Genie are provided by GLib's Object class. So to implement an interface it has to be a sub-type of a GLib Object. A simple interface definition looks like:

interface Test:Object
    prop abstract data : int
    
    def abstract fn ()

This code describes an interface Test which contains two members. "data" is a property, as described above, except that it is declared abstract. Genie will therefore not implement this property, but instead require that classes implementing this interface have a property called "data" that has both get and set accessors - it is required that this be abstract as an interface may not have any data members. The second member "fn" is a method and as above any class implementing this interface must also have a method called fn with the same params and return value

A possible implementation of this interface is:

[indent=4]
init
    var f = new Foo ()
    
    f.fn ()

interface Test:Object
    prop abstract data : int
    
    def abstract fn ()
    
class Foo : Object implements Test
    prop data : int
    
    def fn ()
        print "fn"

Interfaces in Genie may also inherit from other interfaces, for example it may be desirable to say that a "List" interface is also a "Collection" interface, and so the first should inherit from the second. The syntax for this is exactly the same as for describing interface implementation in classes:

interface List : Collection

However this definition of "List" may not now be implemented in a class without "Collection" also being implemented, and so Genie enforces the following style of declaration for a class wishing to implement "List", where all implemented interfaces must be described:

class ListCLass : Object implements Collection, List

Type Inference

Genie has a mechanism called Type Inference, whereby a local variable may be defined using var instead of giving a type, so long as it is unambiguous what type is meant.

var i = 3

var
    a = "happy"
    b = "sad"
    c = "ambivalent"
    
for var I = 1 to 10
    print "looping"    

var can be used for type inferencing in both single lines and blocks as well as for statement initializers.

Parameters of Type (Generics)

Genie includes a runtime generics system, by which a particular instance of a class can be restricted with a particular type or set of types chosen at construction time. This restriction is generally used to require that data stored in the object must be of a particular type, for example in order to implement a list of objects of a certain type. In that example, Genie would make sure that only objects of the requested type could be added to the list, and that on retrieval all objects would be cast to that type.

In Genie, generics are handled while the program is running. When you define a class that can be restricted by a type, there still exists only one class, with each instance customised individually. This is in contrast to C++ which creates a new class for each type restriction required - Genie's is similar to the system used by Java. This has various consequences, most importantly: that static members are shared by the type as a whole, regardless of the restrictions placed on each instance; and that given a class and a subclass, a generic refined by the subclass can be used as a generic refined by the class.

The following code demonstrates how to use the generics system to define a minimal wrapper class and create two instances of different types

[indent=4]
init
    var string_wrapper = new Wrapper of string
    string_wrapper.set_data ("Hello World")
    var s = string_wrapper.get_data ()
    print s

    var int_wrapper = new Wrapper of int
    int_wrapper.set_data (6)
    var data = int_wrapper.get_data ()
    print "test int is  %d", data
    
    var test_wrapper = new Wrapper of TestClass
    var test = new TestClass
    test.accept_object_wrapper (test_wrapper)

class Wrapper of G : Object
    _data : G

    def set_data (data : G)
        _data = data;

    def get_data () : G
        return _data

class TestClass : Object  

    def accept_object_wrapper (w : Wrapper of Object)
        print "accepted object" 

This "Wrapper" class must be restricted with a type in order to instantiate it - in this case the type will be identified as "G", and so instances of this class will store one object of "G" type, and have functions to set or get that object. (The reason for this specific example is to provide reason explain that currently a generic class cannot use properties of its restriction type, and so this class has simple get and set methods instead.)

In order to instantiate this class, a type must be chosen, for example the built in string type (in Genie there is no restriction on what type may be used in a generic). The above example shows one instance of type string and the other of type int.

As you can see, when the data is retrieved from the wrapper, it is assigned to an identifier with no explicit type. This is possible because Genie knows what sort of objects are in each wrapper instance, and therefore can do this work for you.

The fact that Genie does not create multiple classes out of your generic definition means that you can code a method like accept_object_wrapper above. Since all "TestClass" instances are also Objects, the "accept_object_wrapper" function will happily accept the object it is passed, and treat its wrapped object as though it was a Object instance.

Functions

Parameters

A function in Genie is passed zero or more parameters. The default behaviour when a function is called is as follows:

  • Any value type parameters are copied to a location local to the function as it executes.
  • Any reference type parameters are not copied, instead just a reference to them is passed to the function.

This behaviour can be changed with the modifiers ref and out.

`out` from the caller side
you may pass an uninitialized variable to the method and you may expect it to be initialized after the method returns
`out` from callee side
the parameter is considered uninitialized and you have to initialize it
`ref` from caller side
the variable you're passing to the method has to be initialized and it may be changed or not by the method
`ref` from callee side
the parameter is considered initialized and you may change it or not

Heres an example:

[indent=4]

init
    var
        a = 1
        b = 2
        c = 3
        
    Foo.bar (a, out b,  ref c)
    
    print "a=%d, b=%d, c=%d", a,b,c

    
class Foo : Object

    def static bar (a:int, out b: int, ref c: int)
        a = 10
        b = 20
        c = 30

The treatment of each variable will be:

  • "a" is of a value type. The value will be copied into a new memory location local to the function, and so changes to it will not be visible to the caller.
  • "b" is also of a value type, but passed as an out parameter. In this case, the value is not copied, instead a pointer to the data is passed to the function, and so any change to the function parameter will be visible to the calling code.

  • "c" is treated in the same way as "b", the only change is in the signalled intent of the function.

'try...except' Blocks

GLib has a system for managing runtime exceptions called GError. Genie translates this into a form familiar to modern programming languages, but its implementation means it is not quite the same as in Java or C#. It is important to consider when to use this type of error handling - GError is very specifically designed to deal with recoverable runtime errors, i.e. factors that are not known until the program is run on a live system, and that are not fatal to the execution. You should not use GError for problems that can be foreseen, such as reporting that an invalid value has been passed to a function. If a function, for example, requires a number greater than 0 as a parameter, it should fail on negative values using the debugging utils such as assert.

Using exceptions is a matter of:

1) Declaring that a function may raise an error:

    def fn (s:string) raises IOError

2) Throwing the error when appropriate:

    if not check_file (s)
        raise new IOError.FILE_NOT_FOUND ("Requested file could not be found.")

3) Catching the error from the calling code:

    try
        fn("home/jamie/test")
    except ex : IOError
        print "Error: %s", ex.message

All this appears more or less as in other languages, but defining the types of errors allowed is fairly unique. Errors have three components, known as "domain", "code" and message. Messages we have already seen, it is simply a piece of text provided when the error is created. Error domains describe the type of problem, and equates to a subclass of "Exception" in Java or similar. In the above examples we imagined an error domain called "IOError". The third part, the error code is a refinement describing the exact variety of problem encountered. Each error domain has one or more error codes - in the example there is a code called "FILE_NOT_FOUND".

The way to define this information about error types is related to the implementation in glib. In order for the examples here to work, a definition is needed such as:

exception IOError 
    FILE_NOT_FOUND
    FILE_NO_READ_PERMISSION
    FILE_IS_LOCKED

When catching an error, you give the error domain you wish to catch errors in, and if an error in that domain is thrown, the code in the handler is run with the error assigned to the supplied name. From that error object you can extract the error code and message as needed. If you want to catch errors from more than one domain, simply provide extra catch blocks. There is also an optional block that can be placed after a try and any catch blocks, called finally. This code is to be run always at the end of the section, regardless of whether an error was thrown or any catch blocks were executed, even if the error was in fact no handled and will be thrown again. This allows, for example, any resources reserved in the try block be freed regardless of any errors raised. A complete example of these features:

[indent=4]

exception ErrorType1
    CODE_1A

exception ErrorType2
    CODE_2A


init
    try
   
        Test.catcher ()

    except ex:ErrorType1

        print ex.message + " was caught"


class Test:Object 

    def static thrower () raises ErrorType1, ErrorType2 
        raise new ErrorType2.CODE_2A( "Error CODE 2A was raised" )
    

    def static catcher () raises ErrorType1
        try
            thrower ()
        except ex:ErrorType2
            print ex.message
        finally
            print "all exceptions handled"
            

This example has two exceptions, both of which can be thrown by the "thrower" function. Catcher can only throw the second type of error, and so must handle the first type if "thrower" throws it.

Objects

Genie is an Object Orientated language, although it is quite possible to write code without objects. In most cases you will want to use objects to build your application as they have a number of benefits.

Objects may contain fields, properties, methods and events all of which can be made publicly available to other code or can be private and internal to the object. An object's class can also inherit from another class.

All members of an object are publicly accessible, unless they are defined in the class with an identifier starting with an underscore or use the private keyword modifier.

An object is instantiated using the new operator before the object's class, e.g.:

init
    var foo = new Foo()

Fields

Fields are variables declared in the scope of the class.

Constructors

A construct block is used to define a creation method at construction time when an object is being instantiated via the new operator. A class can have many creation methods with either different names or different parameters.

EG

[indent=4]

init
    /* different creation methods can be specified (i.e. those defined with "construct" keyword) where they take different params */
    var foobar = new Foo( 10 )
    
    /* this creation method is same as above but is named differently */
    var foobar2 = new Foo.with_bar( 10 )
    
class Foo:Object
        
    construct( b:int )
        a = b

    construct with_bar( bar:int )
        a = bar

    prop a:int
   
    init     
        print "foo is intitialized"

    final
        print "foo is being destroyed"

An init block is used to wite code that pertains to the initialization of the class - only one of these may be present in any class declaration. An init block declared outside of a class is equivalent to a main function in C and is used to perform initialization of the entire application. init blocks do not have any parameters.

Destructors

A final block is used to perform any finalization of the class when an object instance is destroyed. Final blocks do not take parameters

Properties

Genie classes can also have properties which are identical to GObject properties

Within a class, properties can be defined in various ways, including:

class Foo:Object

    prop name:string
    
    prop readonly count:int
    
    [Description(nick="output property", blurb="This is the output property of the Foo class")]    
    prop output:string
        get
            return "output"
        set
            _name = value   

In the simplest case, the name property is declared without any get/set methods so Genie automatically creates a private field called _name (it always generates a field with the same name as the property but with an underscore prepended) and performs automatic get/set operations for you

Properties that can only be read and never written must use the readonly keyword as in the count property above.

Properties, as in the last case, can of course have user defined get and set definitions as well as GObject short and long descriptions (via the attribute).

In order to use these properties, refer to them in the same way as public members of the class, i.e. (assuming a class called "T"):

    var t = new T()
    t.name = "my name"

Methods

Methods are actions on an object.

Methods always start with the keyword def followed by optional modifiers (private, abstract, inline, extern, virtual, override), the name of the method, a list of method parameters (if any) and lastly, if it returns a value, the type of the return value.

Here's an example

[indent=4]

init
    var f = new Foo ()
    print f.bar ("World")

class Foo

    def static bar( name:string):string
        return "Hello " + name 

The above defines a method called bar which takes a string parameter called name and returns a string.

All methods, properties and events can also take modifiers to define further options. Modifiers are always placed between the def keyword and the method name. A full explanation of these modifiers are as follows:

private - indicates that this method/property/event is not accessible from outside the defining class. It is not necessary to include this modifier if its name starts with an underscore as anything that starts with that is always deemed private

extern - indicates that this method is defined outside of genie/vala sources and is often implemented in C. This handy feature allows methods to be defined in C (or assembler) where necessary - c files to be included in the final executable have to be passed to valac in order for this to work.

inline - hints to the compiler that it should inline this method to improve performance. This is useful for very small methods

static - indicates a method that can be run without needing an instance of the class. Static methods never change the state or properties of a class instance as a result of this.

Sub-Typing

Sub-typing in Genie is pretty much the same as in other OO languages except that multiple inheritance is not supported. Although a class in Genie can only descend from one other class, it can implement many interfaces.

abstract - indicates that the method body is not defined in the current class but any subclass must implement this method. This is very useful when defining interfaces which classes can implement

virtual - indicates that this method may be overriden in subclasses

override - indicates to override the super class virtual method of the same name and replace it with the new definition. If no method with the virtual modifier exists in the superclass then the compiler will signal an error.

Sub-Typing 'Object'

A simple example:

class Foo:Object
        
    /* property declaration */
    prop p1:int
        
    /* method declaration */
    def bar( name:string ) 
        print "hello %s p1 is %d", name, p1

    /* method with return value */
    def bar2():string
        return "bar2 method was called"

The above example shows that class Foo is a subclass of object (which means it inherits all the features of GLib's GObject such as interfaces and events).

Events (Signals)

Events (aka Signals) are an intrisic feature of GObjects and all classes which are derived from Object can have them. An event is assigned handlers (code blocks) and when the event is triggered those event code blocks are executed

An event is defined as a member of a class, and appears similar to a method with no body. Event handlers can then be added to the event using the special += operator.

[indent=4]

init

    var f = new Foo()
    
    /* set event handler */
    f.my_event += def (t, a)
        print "event was detected with value %d", a
    
    /* fire the event */
    f.my_event (5)


class Foo:Object
    event my_event( a:int )

Also depicted above is a closure which provides the event handler. A closure allows a code block to be inlined into the source. The alternative to a closure is to provide a callback function for the event handler.

The reason there are two arguments to the handler (t and a) is that whenever a signal is emitted, the object on which it is emitted is passed as the first argument to the handler. The second argument is the one that the signal says it will provide.

We trigger the event by calling it like a method on the class.

NB: Currently Genie doesn't support signals with return values + all events are never private

Special Operators

You can check if a object is of a given Object type by using the isa operator:

init
    var o = new list of string
    if o isa Object
        print "a list of string is an object"
    if o isa list of Object
        print "a list of string is a list of Object"
    if o isa Gee.Iterable
        print "a list of string implements Iterable"

Collections

Genie has a number of colleciton types built in including arrays, lists and dicts.

Gee provides the support for list and dict types in Genie so if you make use of these you will need to have libgee installed (as will anyone who wants to run your compiled programs). if you dont make use of list and dicts then you do not need this library

Lists (ArrayList)

Lists are ordered collections of items, accesible by numeric index. They can grow and shrink automatically as items are added/removed. In essence a list is really a dynamic array.

Lists are generic in nature and use the same syntax as other generics. A new list is therefore created in the same way:

    var l = new list of string

the above creates a string list but as with generics all other types are supported too. Elements in a list can be accessed and changed just like it was an array. As lists are also iterables 'for..in' can be used to iterate over them too.

[indent=4]

init

    /* test lists */
    var l = new list of string
    
    l.add ("Genie")    
    l.add ("Rocks")
    l.add ("The")
    l.add ("World")

    for s in l
        print s
    
    print " "
    
    l[2] = "My"
    
    for s in l
        print s

    print " "

Dicts (HashMap)

Dicts (also known as dictionaries) are an unordered collection of items accesible by index of arbitry type.

They can be used as lookup tables and to quickly map values of one types to another values of the same or another type

In other languages dicts may be known as hash maps or hash tables.

dicts typically consist of key/value pairs and when creating a new one you need to specify the key and value types :

    var d = new dict of string,string

The above example creates a dict whose key is a string and whose value is also a string

The types can be different of course. Say we want to implement a phone book then we would have the key as a string (name) and the phone number as an int. We would create this using:

    var d = new dict of string,int

Dicts, like lists, can be accessed by key index and its keys and values can also be iterated over using the standard for..in statement. You can add new entires by using the add method or more easily by using d[key] = value

Here is a full example:

[indent=4]

init
 
    /* test dicts */
    var d = new dict of string,string

    /* add or change entries with following */
    d["Genie"] = "Great"
    d["Vala"] = "Rocks"
    
    
    /* access entires using d[key] */
    /* note that instead of "d.get_keys ()" it is "d.keys" in newer Versions of Gee */
    for s in d.get_keys ()
        print "%s => %s", s, d[s]

Iterators

A for loop can be used to iterate over a class's collection by adding get() and next() methods to the class. See Methods with Syntax Support in the Vala tutorial.

Searches

The in operator can be used to search if an item is in a class's collection by adding a contains() method to the class. See Methods with Syntax Support in the Vala tutorial.

Interfacing with a C Library

Nullable Types

By default, Genie will make sure that all reference point to actual objects. This means that you can't arbitrarily assign null to a variable. If it is allowable for a reference to be null you should declare the type with a ? modifier. This allows you to assert which types may be null, and so hopefully avoid related errors in your code.

This modifier can be placed both on parameter types and return types of functions:

/* allows null to be passed as param and allows null to be returned */
def fn_allow_nulls (param : string?) : string?
        return param
        
/* attempting to pass null as param will lead to that function aborting. Likewise it may not return a null value */     
def fn_no_null (param : string) : string
        return param    

These checks are performed at run time. For release versions of a program, when the code is fully debugged, the checks can be disabled. See the valac documentation for how to do this.

Arrays

An array is an ordered collection of items of a fixed size. You cannot change the size of an array so additional elements cannot be added or removed (although existing elements can have their value changed)

An array is created by using "array of type name" followed by the fixed size of the array e.g. var a = new array of int[10] to create an array of 10 integers. The length of such an array can be obtained by the length member variable e.g. var count = a.length.

Arrays can take initilializers too, in which case theres no need to specify a length :

tokens : array of string = {"This", "Is", "Genie"}

Arrays can be constant too:

const int_array : array of int = {1, 2, 3}

Individual elements in an array can be accessed by their position index. They can also be iterated over using the standard for..in statement. Heres an example demonstrating this:

[indent=4]
const int_array : array of int = {1, 2, 3}

init
    tokens : array of string = {"This", "Is", "Genie"}
    var sa = new array of string[3]
    
    var i = 0
    for s in tokens
        print s
        sa[i] = s
        i++
        
    sa[2] = "Cool"    
        
    for s in sa
        print s
        
    for i in int_array
        print "number %d", i

You can have arrays of any type including nested types. Arrays of structs are also possible:

[indent=4]
init

    var f = new Foo ()

    try
        var opt_context = new OptionContext ("- Test array of structs")
        opt_context.set_help_enabled (true)
        opt_context.add_main_entries (f.options, null)
        opt_context.parse (ref args)
        
    except e:OptionError
        stdout.printf ("%s\n", e.message)
        stdout.printf ("Run '%s --help' to see a full list of available command line options.\n", args[0])

    
    
class Foo : Object
    
    [NoArrayLength ()]
    test_directories : static array of string
    
    const options : array of OptionEntry = {{ "testarg", 0, 0, OptionArg.FILENAME_ARRAY, ref test_directories, "test DIRECTORY", "DIRECTORY..." }, {null}}
    

Structs

A struct type, i.e. a compound value type. A Genie struct may have methods in a limited way and may also have private data.

struct StructName
    a : string
    i : int

The example struct StructName can be initialized as follows:

var mystruct = StructName()
var mystruct = StructName() {"mystring", 1}
var mystruct = StructName() {
    a = "mystring", 
    i = 1
}

Callbacks

A Genie function can be passed as a callback to a library function. The identifier of the Genie callback function needs to be passed as an argument to the library function:

init
	CLibrary.library_function( my_callback )

def my_callback( example:string )
	print example

Concurrency

Asynchronous Methods

Asynchronous methods are methods whose execution can be paused and resumed under the control of the programmer. They are often used in the main thread of an application where a method needs to wait for an external slow task to complete, but must not stop other processing from happening. (For example, one slow operation must not freeze the whole GUI). When the method has to wait, it gives control of the CPU back to its caller (i.e. it yields), but it arranges to be called back to resume execution when data becomes ready. External slow tasks that async methods might wait for include: waiting for data from a remote server, or waiting for calculations in another thread to complete, or waiting for data to load from a disk drive.

Asynchronous methods are normally used with a GLib main loop running, because idle callbacks are used to handle some of the internal callbacks. However under certain conditions async may be used without the GLib main loop, for example if the async methods always yield and Idle.add() is never used.

Asynchronous methods are designed for interleaving the processing of many different long-lived operations within a single thread. They do not by themselves spread the load out over different threads. However, an async method may be used to control a background thread and to wait for it to complete, or to queue operations for a background thread to process.

Async methods in Genie use the GIO library to handle the callbacks, so must be built with the --pkg=gio-2.0 option.

An asynchronous method is defined with the async keyword. For example:

    def async display_jpeg(fname : string)
        // Some code to load JPEG in a background thread here and display it when loaded
        // ...

or

    def async fetch_webpage(url : string, out text : string) : int raises IOError
        // Some code to fetch a webpage asynchronously and when ready return the
        // HTTP status code and put the page contents in 'text'
        text = result
        return status

The method may take arguments and return a value like any other method. It may use a yield statement at any time to give control of the CPU back to its caller.

An async method is called like that:

    display_jpeg("test.jpg")

or

    display_jpeg.begin("test.jpg")

These forms are equivalent and start the async method with the given arguments.

When an asynchronous method starts running, it takes control of the CPU until it reaches its first yield statement, at which point it returns to the caller. When the method is resumed, it continues execution immediately after that yield statement. There are several common ways to use yield:

This form gives up control, but arranges for the GLib main loop to resume the method when there are no more events to process:

    Idle.add(fetch_webpage.callback)
    yield

This form gives up control, and stores the callback details for some other code to use to resume the method's execution:

    callback : SourceFunc = fetch_webpage.callback
    yield

Some code elsewhere must now call the stored SourceFunc in order for the method to be resumed. This could be done by scheduling the GLib main loop to run it:

    Idle.add((owned) callback)

or alternatively a direct call may be made if the caller is running in the main thread:

    callback()

If the direct call above is used, then the resumed asynchronous method takes control of the CPU immediately and runs until its next yield before returning to the code that executed callback(). The Idle.add() method is useful if the callback must be made from a background thread, e.g. to resume the async method after completion of some background processing. (The (owned) cast is necessary to avoid a warning about copying delegates.)

The third common way of using yield is when calling another asynchronous method, for example:

    yield display_jpeg(fname)

or

    text : string
    var status = yield fetch_webpage(url, out text)

In both cases, the calling method gives up control of the CPU and does not resume until the called method completes. The yield statement automatically registers a callback with the called method to make sure that the caller resumes correctly. The automatic callback also collects the return value from the called method.

When this yield statement executes, control of the CPU first passes to the called method which runs until its first yield and then drops back to the calling method, which completes the yield statement itself, and then gives back control to its own caller.

Attributes

An attributes is metadata that specifies alternative handling of the source below it.

Attributes may be used to:

  • Change indentation from tabs to spaces
  • Modify the generated C code (CCode)
  • Specify a variant type of structs (SimpleType, FloatingType, etc)

  • Provide a description of an object

Attributes are placed in square brackets "[]" immediately before the symbol they modify. Multiple attributes applying to the same symbol can be separated by a comma;

  [CCode (cname="dbCallbackStruct", cprefix="Foo__"), Compact]
  class Callback

With exception of the indent attribute, all attributes only apply to the following symbol and its children.

For a complete and current list of available attributes, see the Vala Manual.

Advanced Memory Management

There are broadly two types of data in Genie: Reference types and Value types. These names describe how instances of the types are passed around the system - a value type is copied whenever it is assigned to a new identifier, a reference type is not copied, instead the new identifier is simply a new reference to the same object.

Genie's value types are the basic simple data types, and the compound type "struct". The simple types are:

  • char, uchar
  • int, uint
  • float, double
  • bool (boolean)
  • unichar (Unicode character)
  • string

The reference types are all types declared as a class, regardless of whether they are descended from GLib's Object. Genie will ensure that when you pass an object by reference the system will keep track of the number of references currently alive in order to manage memory for you. If you define your own classes as being descending from GLib's Object, your types will do this as well. Also all arrays are created as reference types, as is the string type, representing a UTF-8 encoded string.

Weak References

Genie's memory management is based on automatic reference counting. Each time an object is assigned to a variable its internal reference count is increased by 1, each time a variable referencing an object goes out of scope its internal reference count is decreased by 1. If the reference count reaches 0 the object will be freed.

However, it is possible to form a reference cycle with your data structures. For example, with a tree data structure where a child node holds a reference to its parent and vice versa, or a doubly-linked list where each element holds a reference to its predecessor and the predecessor holds a reference to its successor.

In these cases objects could keep themselves alive simply by referencing to each other, even though they should be freed. To break such a reference cycle you can use the weak modifier for one of the references:

class Node : Object
    prev : weak Node
    next : Node

This topic is explained in detail on this page (Vala page, but relevant for Genie, too): Vala's Memory Management Explained. Important note from the page is that at the moment "unowned" and "weak" can be used interchangeably. However, you should use weak only for breaking reference cycles and unowned only for ownership issues as described in the linked page.

Unowned References

Normally when creating an object in Genie you are returned a reference to it. Specifically this means that as well as being passed a pointer to the object in memory, it is also recorded in the object itself that this pointer exists. Similarly, whenever another reference to the object is created, this is also recorded. As an object knows how many references there are to it, it can automatically be removed when needed. This is the basis of Genie's memory management.

Unowned References conversely are not recorded in the object they reference. This allows the object to be removed when it logically should be, regardless of the fact that there might be still references to it. The usual way to achieve this is with a function defined to return a weak reference, e.g.:

class Test
    o : Object
    def get_unowned_ref () : unowned Object
        o = new Object()
        return o

When calling this function, in order to collect a reference to the returned object, you must expect to receive an unowned reference:

o : unowned Object = get_unowned_ref()

The reason for this seemingly overcomplicated example because of the concept of ownership.

  • If the Object "o" was not stored in the class, then when the function "get_unowned_ref" returned, "o" would become unowned (i.e. there would be no references to it). If this were the case, the object would be deleted and the function would never return a valid reference.
  • If the return value was not defined as unowned, the ownership would pass to the calling code. The calling code is, however, expecting an unowned reference, which cannot receive the ownership.

If the calling code is written as

o : Object = get_unowned_ref();

Genie will try to either obtain a reference of or a duplicate of the instance the unowned reference pointing to.

In contrast to normal methods, properties always have unowned return value. That means you can't return a new object created within the get method. That also means, you can't use an owned return value from a method call. The somewhat irritating fact is because of that a property value is owned by the object that HAS this property. A call to obtain this property value should not steal or reproduce (by duplicating, or increasing the reference count of) the value from the object side.

On the other hand, this is perfectly fine

prop property : string
    get
        return getter_method() // GOOD: getter_method returns an unowned value

def getter_method() : unowned string
    return "sometext"
    // Don't be alarmed that the text is not assigned to any strong variable.
    // Literal strings in Genie are always owned by the program module itself,
    // and exist as long as the module is in memory

The unowned modifier can be used to make automatic property's storage unowned.

_property : unowned Object
prop property : Object
    get
        return _property

The keyword owned can be used to specifically ask a property to return an owned reference of the value, therefore causing the property value be reproduced in the object side. Think twice before adding the owned keyword. Is it a property or simply a get_xxx method? There may also be problems in your design.

prop property : Object
    owned get
        return new Object

Unowned references play a similar role to pointers, described next. They are however much simpler to use, and can be easily combined with normal references, making them useful more easily and more often. However, in general they should not be widely used in the programs unless you know what you are doing.

Ownership Transfer

The keyword '(owned)', like in Vala, is used to transfer ownership.

As a prefix of a parameter (or property) type it means that ownership of the object is transferred. As an operator, it can be used to avoid copies of non-reference counting classes. e.g.

var s = "hello"

/* s will be null when owner is transferred */
t : string = (owned) s

print t
print s

This means that s will be set to null and t inherits the reference/ownership of s.

Pointers

Pointers are Genie's way of allowing manual memory management. Normally when you create an instance of a type you receive a reference to it, and Genie will take care of destroying the instance when there are no more references left to it. By requesting instead a pointer to an instance, you take responsibility for destroying the instance when it is no longer wanted, and therefore get greater control over how much memory is used.

This functionality is not necessarily needed most of the time, as modern computers are usually fast enough to handle reference counting and have enough memory that small inefficiencies are not important. The times when you might resort to manual memory management are:

  • When you specifically want to optimise part of a program.
  • When you are dealing with an external library that does not implement reference counting for memory management (probably meaning one not based on GObject.)

In order to create an instance of a type, and receive a pointer to it you must add the * suffix to the type declaration :

o : object* = new Object

In order to access members of that instance:

o->function_1
o->data_1

In order to free the memory pointed to:

delete o;

Using Genie and Vala together

This is actually very easy as in it seems to just work as you'd expect (I'm not an expert so maybe someone could add in any oddities that may be faced when doing this.)

Here's a quick example:

// Vala: test_vg.vala
public class TestVala
{
        public void vala_print(string str)
        {
                stdout.printf("%s", str);
        }
}

void main()
{
        test("Hello vala+genie");

        var test_class = new TestGenie();

        test_class.vala_print("call to inherited parent member from Vala\n");
        test_class.genie_print("call to member from Genie\n");
}

[indent=4]
// Genie: test_gv.gs
def test (str : string)
    print "%s", str

class TestGenie : TestVala
    def genie_print(str : string)
        stdout.printf("GENIE: ")
        vala_print(str)

compile with:

$ valac -o test_vala-genie test_vg.vala test_gv.gs

Some notes:

  • File names must differ even if they're in different paths.

More Resources

Projects/Genie (last edited 2016-07-29 13:22:05 by AlThomas)